home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / lib / mozilla-thunderbird / components / nsExtensionManager.js < prev    next >
Encoding:
Text File  |  2007-04-03  |  267.6 KB  |  7,173 lines

  1. /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is the Extension Manager.
  16.  *
  17.  * The Initial Developer of the Original Code is Ben Goodger.
  18.  * Portions created by the Initial Developer are Copyright (C) 2004
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *  Ben Goodger <ben@mozilla.org> (Google Inc.)
  23.  *  Benjamin Smedberg <benjamin@smedbergs.us>
  24.  *  Jens Bannmann <jens.b@web.de>
  25.  *  Robert Strong <robert.bugzilla@gmail.com>
  26.  *  Daniel Veditz <dveditz@mozilla.com>
  27.  *
  28.  * Alternatively, the contents of this file may be used under the terms of
  29.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  30.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  31.  * in which case the provisions of the GPL or the LGPL are applicable instead
  32.  * of those above. If you wish to allow use of your version of this file only
  33.  * under the terms of either the GPL or the LGPL, and not to allow others to
  34.  * use your version of this file under the terms of the MPL, indicate your
  35.  * decision by deleting the provisions above and replace them with the notice
  36.  * and other provisions required by the GPL or the LGPL. If you do not delete
  37.  * the provisions above, a recipient may use your version of this file under
  38.  * the terms of any one of the MPL, the GPL or the LGPL.
  39.  *
  40.  * ***** END LICENSE BLOCK ***** */
  41.  
  42. //
  43. // TODO:
  44. // - better logging
  45. //
  46.  
  47. const nsIExtensionManager             = Components.interfaces.nsIExtensionManager;
  48. const nsIAddonUpdateCheckListener     = Components.interfaces.nsIAddonUpdateCheckListener;
  49. const nsIUpdateItem                   = Components.interfaces.nsIUpdateItem;
  50. const nsILocalFile                    = Components.interfaces.nsILocalFile;
  51. const nsILineInputStream              = Components.interfaces.nsILineInputStream;
  52. const nsIInstallLocation              = Components.interfaces.nsIInstallLocation;
  53. const nsIURL                          = Components.interfaces.nsIURL
  54. // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  55. // it has been removed will cause a crash on Mac OS X - bug 292823
  56. const nsIDirectoryEnumerator          = Components.interfaces.nsIDirectoryEnumerator;
  57.  
  58. const PREF_EM_APP_EXTENSIONS_VERSION  = "app.extensions.version";
  59. const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
  60. const PREF_UPDATE_COUNT               = "extensions.update.count";
  61. const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
  62. const PREF_EM_IGNOREMTIMECHANGES      = "extensions.ignoreMTimeChanges";
  63. const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
  64. const PREF_EM_LAST_SELECTED_SKIN      = "extensions.lastSelectedSkin";
  65. const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
  66. const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
  67. const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
  68. const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
  69. const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
  70. const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
  71. const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
  72. const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
  73. const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
  74. const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
  75. const PREF_XPINSTALL_STATUS_DLG_SKIN  = "xpinstall.dialog.progress.skin";
  76.  
  77. const DIR_EXTENSIONS                  = "extensions";
  78. const DIR_CHROME                      = "chrome";
  79. const DIR_STAGE                       = "staged-xpis";
  80. const FILE_EXTENSIONS                 = "extensions.rdf";
  81. const FILE_EXTENSION_MANIFEST         = "extensions.ini";
  82. const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
  83. const FILE_AUTOREG                    = ".autoreg";
  84. const FILE_INSTALL_MANIFEST           = "install.rdf";
  85. const FILE_CONTENTS_MANIFEST          = "contents.rdf";
  86. const FILE_CHROME_MANIFEST            = "chrome.manifest";
  87.  
  88. const UNKNOWN_XPCOM_ABI               = "unknownABI";
  89.  
  90. const FILE_LOGFILE                    = "extensionmanager.log";
  91.  
  92. const FILE_DEFAULT_THEME_JAR          = "classic.jar";
  93.  
  94. const KEY_PROFILEDIR                  = "ProfD";
  95. const KEY_PROFILEDS                   = "ProfDS";
  96. const KEY_APPDIR                      = "XCurProcD";
  97. const KEY_TEMPDIR                     = "TmpD";
  98.  
  99. const EM_ACTION_REQUESTED_TOPIC       = "em-action-requested";
  100. const EM_ITEM_INSTALLED               = "item-installed";
  101. const EM_ITEM_UPGRADED                = "item-upgraded";
  102. const EM_ITEM_UNINSTALLED             = "item-uninstalled";
  103. const EM_ITEM_ENABLED                 = "item-enabled";
  104. const EM_ITEM_DISABLED                = "item-disabled";
  105. const EM_ITEM_CANCEL                  = "item-cancel-action";
  106.  
  107. const OP_NONE                         = "";
  108. const OP_NEEDS_INSTALL                = "needs-install";
  109. const OP_NEEDS_UPGRADE                = "needs-upgrade";
  110. const OP_NEEDS_UNINSTALL              = "needs-uninstall";
  111. const OP_NEEDS_ENABLE                 = "needs-enable";
  112. const OP_NEEDS_DISABLE                = "needs-disable";
  113.  
  114. const KEY_APP_PROFILE                 = "app-profile";
  115. const KEY_APP_GLOBAL                  = "app-global";
  116.  
  117. const CATEGORY_INSTALL_LOCATIONS      = "extension-install-locations";
  118.  
  119. const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
  120. const PREFIX_NS_CHROME                = "http://www.mozilla.org/rdf/chrome#";
  121. const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
  122. const PREFIX_EXTENSION                = "urn:mozilla:extension:";
  123. const PREFIX_THEME                    = "urn:mozilla:theme:";
  124. const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
  125. const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
  126. const RDFURI_DEFAULT_THEME            = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}";
  127. const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
  128.  
  129. const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
  130. const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
  131. const URI_XPINSTALL_CONFIRM_DIALOG    = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
  132. const URI_FINALIZE_DIALOG             = "chrome://mozapps/content/extensions/finalize.xul";
  133. const URI_EXTENSIONS_PROPERTIES       = "chrome://mozapps/locale/extensions/extensions.properties";
  134. const URI_BRAND_PROPERTIES            = "chrome://branding/locale/brand.properties";
  135. const URI_DOWNLOADS_PROPERTIES        = "chrome://mozapps/locale/downloads/downloads.properties";
  136. const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
  137.  
  138. const INSTALLERROR_SUCCESS               = 0;
  139. const INSTALLERROR_INVALID_VERSION       = -1;
  140. const INSTALLERROR_INVALID_GUID          = -2;
  141. const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
  142. const INSTALLERROR_PHONED_HOME           = -4;
  143. const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
  144.  
  145. const MODE_WRONLY   = 0x02;
  146. const MODE_CREATE   = 0x08;
  147. const MODE_APPEND   = 0x10;
  148. const MODE_TRUNCATE = 0x20;
  149.  
  150. const PERMS_FILE      = 0644;
  151. const PERMS_DIRECTORY = 0755;
  152.  
  153. var gApp  = null;
  154. var gPref = null;
  155. var gRDF  = null;
  156. var gOS   = null;
  157. var gXPCOMABI             = null;
  158. var gOSTarget             = null;
  159. var gConsole              = null;
  160. var gInstallManifestRoot  = null;
  161. var gVersionChecker       = null;
  162. var gLoggingEnabled       = null;
  163.  
  164. /** 
  165.  * Valid GUIDs fit this pattern.
  166.  */
  167. var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
  168.  
  169. // shared code for suppressing bad cert dialogs
  170. //@line 40 "/build/buildd/mozilla-thunderbird-1.5.0.10/build-dir/mozilla/toolkit/mozapps/extensions/src/../../shared/src/badCertHandler.js"
  171.  
  172. /**
  173.  * Only allow built-in certs for HTTPS connections.  See bug 340198.
  174.  */
  175. function checkCert(channel) {
  176.   if (!channel.originalURI.schemeIs("https"))  // bypass
  177.     return;
  178.  
  179.   const Ci = Components.interfaces;  
  180.   var cert =
  181.       channel.securityInfo.QueryInterface(Ci.nsISSLStatusProvider).
  182.       SSLStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
  183.  
  184.   var issuer = cert.issuer;
  185.   while (issuer && !cert.equals(issuer)) {
  186.     cert = issuer;
  187.     issuer = cert.issuer;
  188.   }
  189.  
  190.   if (!issuer || issuer.tokenName != "Builtin Object Token")
  191.     throw "cert issuer is not built-in";
  192. }
  193.  
  194. /**
  195.  * This class implements nsIBadCertListener.  It's job is to prevent "bad cert"
  196.  * security dialogs from being shown to the user.  It is better to simply fail
  197.  * if the certificate is bad. See bug 304286.
  198.  */
  199. function BadCertHandler() {
  200. }
  201. BadCertHandler.prototype = {
  202.  
  203.   // nsIBadCertListener
  204.   confirmUnknownIssuer: function(socketInfo, cert, certAddType) {
  205.     LOG("EM BadCertHandler: Unknown issuer");
  206.     return false;
  207.   },
  208.  
  209.   confirmMismatchDomain: function(socketInfo, targetURL, cert) {
  210.     LOG("EM BadCertHandler: Mismatched domain");
  211.     return false;
  212.   },
  213.  
  214.   confirmCertExpired: function(socketInfo, cert) {
  215.     LOG("EM BadCertHandler: Expired certificate");
  216.     return false;
  217.   },
  218.  
  219.   notifyCrlNextupdate: function(socketInfo, targetURL, cert) {
  220.   },
  221.  
  222.   // nsIChannelEventSink
  223.   onChannelRedirect: function(oldChannel, newChannel, flags) {
  224.     // make sure the certificate of the old channel checks out before we follow
  225.     // a redirect from it.  See bug 340198.
  226.     checkCert(oldChannel);
  227.   },
  228.  
  229.   // nsIInterfaceRequestor
  230.   getInterface: function(iid) {
  231.     if (iid.equals(Components.interfaces.nsIBadCertListener) ||
  232.         iid.equals(Components.interfaces.nsIChannelEventSink))
  233.       return this;
  234.  
  235.     Components.returnCode = Components.results.NS_ERROR_NO_INTERFACE;
  236.     return null;
  237.   },
  238.  
  239.   // nsISupports
  240.   QueryInterface: function(iid) {
  241.     if (!iid.equals(Components.interfaces.nsIBadCertListener) &&
  242.         !iid.equals(Components.interfaces.nsIChannelEventSink) &&
  243.         !iid.equals(Components.interfaces.nsIInterfaceRequestor) &&
  244.         !iid.equals(Components.interfaces.nsISupports))
  245.       throw Components.results.NS_ERROR_NO_INTERFACE;
  246.     return this;
  247.   }
  248. };
  249. //@line 171 "/build/buildd/mozilla-thunderbird-1.5.0.10/build-dir/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  250.  
  251. /**
  252.  * Creates a Version Checker object.
  253.  * @returns A handle to the global Version Checker service.
  254.  */
  255. function getVersionChecker() {
  256.   if (!gVersionChecker) {
  257.     gVersionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"]
  258.                                 .getService(Components.interfaces.nsIVersionComparator);
  259.   }
  260.   return gVersionChecker;
  261. }
  262.  
  263. var BundleManager = { 
  264.   /**
  265.   * Creates and returns a String Bundle at the specified URI
  266.   * @param   bundleURI
  267.   *          The URI of the bundle to load
  268.   * @returns A nsIStringBundle which was retrieved.
  269.   */
  270.   getBundle: function(bundleURI) {
  271.     var sbs = Components.classes["@mozilla.org/intl/stringbundle;1"]
  272.                         .getService(Components.interfaces.nsIStringBundleService);
  273.     return sbs.createBundle(bundleURI);
  274.   },
  275.   
  276.   _appName: "",
  277.   
  278.   /**
  279.    * The Application's display name.
  280.    */
  281.   get appName() {
  282.     if (!this._appName) {
  283.       var brandBundle = this.getBundle(URI_BRAND_PROPERTIES)
  284.       this._appName = brandBundle.GetStringFromName("brandShortName");
  285.     }
  286.     return this._appName;
  287.   }
  288. };
  289.  
  290. ///////////////////////////////////////////////////////////////////////////////
  291. //
  292. // Utility Functions
  293. //
  294. function EM_NS(property) {
  295.   return PREFIX_NS_EM + property;
  296. }
  297.  
  298. function CHROME_NS(property) {
  299.   return PREFIX_NS_CHROME + property;
  300. }
  301.  
  302. function EM_R(property) {
  303.   return gRDF.GetResource(EM_NS(property));
  304. }
  305.  
  306. function EM_L(literal) {
  307.   return gRDF.GetLiteral(literal);
  308. }
  309.  
  310. function EM_I(integer) {
  311.   return gRDF.GetIntLiteral(integer);
  312. }
  313.  
  314. /**
  315.  * Gets a preference value, handling the case where there is no default.
  316.  * @param   func
  317.  *          The name of the preference function to call, on nsIPrefBranch
  318.  * @param   preference
  319.  *          The name of the preference
  320.  * @param   defaultValue
  321.  *          The default value to return in the event the preference has 
  322.  *          no setting
  323.  * @returns The value of the preference, or undefined if there was no
  324.  *          user or default value.
  325.  */
  326. function getPref(func, preference, defaultValue) {
  327.   try {
  328.     return gPref[func](preference);
  329.   }
  330.   catch (e) {
  331.   }
  332.   return defaultValue;
  333. }
  334.  
  335. /**
  336.  * Initializes a RDF Container at a URI in a datasource.
  337.  * @param   datasource
  338.  *          The datasource the container is in
  339.  * @param   root
  340.  *          The RDF Resource which is the root of the container.
  341.  * @returns The nsIRDFContainer, initialized at the root.
  342.  */
  343. function getContainer(datasource, root) {
  344.   var ctr = Components.classes["@mozilla.org/rdf/container;1"]
  345.                       .createInstance(Components.interfaces.nsIRDFContainer);
  346.   ctr.Init(datasource, root);
  347.   return ctr;
  348. }
  349.  
  350. /**
  351.  * Gets a RDF Resource for item with the given ID
  352.  * @param   id
  353.  *          The GUID of the item to construct a RDF resource to the 
  354.  *          active item for
  355.  * @returns The RDF Resource to the Active item. 
  356.  */
  357. function getResourceForID(id) {
  358.   return gRDF.GetResource(PREFIX_ITEM_URI + id);
  359. }
  360.  
  361. /**
  362.  * Construct a nsIUpdateItem with the supplied metadata
  363.  * ...
  364.  */
  365. function makeItem(id, version, locationKey, minVersion, maxVersion, name, 
  366.                   updateURL, updateHash, iconURL, updateRDF, type) {
  367.   var item = Components.classes["@mozilla.org/updates/item;1"]
  368.                        .createInstance(Components.interfaces.nsIUpdateItem);
  369.   item.init(id, version, locationKey, minVersion, maxVersion, name,
  370.             updateURL, updateHash, iconURL, updateRDF, type);
  371.   return item;
  372. }
  373.  
  374. /**
  375.  * Gets the specified directory at the speciifed hierarchy under a 
  376.  * Directory Service key. 
  377.  * @param   key
  378.  *          The Directory Service Key to start from
  379.  * @param   pathArray
  380.  *          An array of path components to locate beneath the directory 
  381.  *          specified by |key|
  382.  * @return  nsIFile object for the location specified. If the directory
  383.  *          requested does not exist, it is created, along with any
  384.  *          parent directories that need to be created.
  385.  */
  386. function getDir(key, pathArray) {
  387.   return getDirInternal(key, pathArray, true);
  388. }
  389.  
  390. /**
  391.  * Gets the specified directory at the speciifed hierarchy under a 
  392.  * Directory Service key. 
  393.  * @param   key
  394.  *          The Directory Service Key to start from
  395.  * @param   pathArray
  396.  *          An array of path components to locate beneath the directory 
  397.  *          specified by |key|
  398.  * @return  nsIFile object for the location specified. If the directory
  399.  *          requested does not exist, it is NOT created.
  400.  */
  401. function getDirNoCreate(key, pathArray) {
  402.   return getDirInternal(key, pathArray, false);
  403. }
  404.  
  405. /**
  406.  * Gets the specified directory at the speciifed hierarchy under a 
  407.  * Directory Service key. 
  408.  * @param   key
  409.  *          The Directory Service Key to start from
  410.  * @param   pathArray
  411.  *          An array of path components to locate beneath the directory 
  412.  *          specified by |key|
  413.  * @param   shouldCreate
  414.  *          true if the directory hierarchy specified in |pathArray|
  415.  *          should be created if it does not exist,
  416.  *          false otherwise.
  417.  * @return  nsIFile object for the location specified. 
  418.  */
  419. function getDirInternal(key, pathArray, shouldCreate) {
  420.   var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
  421.                               .getService(Components.interfaces.nsIProperties);
  422.   var dir = fileLocator.get(key, nsILocalFile);
  423.   for (var i = 0; i < pathArray.length; ++i) {
  424.     dir.append(pathArray[i]);
  425.     if (shouldCreate && !dir.exists())
  426.       dir.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  427.   }
  428.   dir.followLinks = false;
  429.   return dir;
  430. }
  431.  
  432. /**
  433.  * Gets the file at the speciifed hierarchy under a Directory Service key.
  434.  * @param   key
  435.  *          The Directory Service Key to start from
  436.  * @param   pathArray
  437.  *          An array of path components to locate beneath the directory 
  438.  *          specified by |key|. The last item in this array must be the
  439.  *          leaf name of a file.
  440.  * @return  nsIFile object for the file specified. The file is NOT created
  441.  *          if it does not exist, however all required directories along 
  442.  *          the way are.
  443.  */
  444. function getFile(key, pathArray) {
  445.   var file = getDir(key, pathArray.slice(0, -1));
  446.   file.append(pathArray[pathArray.length - 1]);
  447.   return file;
  448. }
  449.  
  450. /**
  451.  * Gets the descriptor of a directory as a relative path to common base
  452.  * directories (profile, user home, app install dir, etc).
  453.  *
  454.  * @param   itemLocation
  455.  *          The nsILocalFile representing the item's directory.
  456.  * @param   installLocation the nsIInstallLocation for this item
  457.  */
  458. function getDescriptorFromFile(itemLocation, installLocation) {
  459.   var baseDir = installLocation.location;
  460.  
  461.   if (baseDir && baseDir.contains(itemLocation, true)) {
  462.     return "rel%" + itemLocation.getRelativeDescriptor(baseDir);
  463.   }
  464.  
  465.   return "abs%" + itemLocation.persistentDescriptor;
  466. }
  467.  
  468. function getAbsoluteDescriptor(itemLocation) {
  469.   return itemLocation.persistentDescriptor;
  470. }
  471.  
  472. /**
  473.  * Initializes a Local File object based on a descriptor
  474.  * provided by "getDescriptorFromFile".
  475.  *
  476.  * @param   descriptor
  477.  *          The descriptor that locates the directory
  478.  * @param   installLocation
  479.  *          The nsIInstallLocation object for this item.
  480.  * @returns The nsILocalFile object representing the location of the item
  481.  */
  482. function getFileFromDescriptor(descriptor, installLocation) {
  483.   var location = Components.classes["@mozilla.org/file/local;1"]
  484.                            .createInstance(nsILocalFile);
  485.  
  486.   var m = descriptor.match(/^(abs|rel)\%(.*)$/);
  487.   if (!m)
  488.     throw Components.results.NS_ERROR_INVALID_ARG;
  489.  
  490.   if (m[1] == "rel") {
  491.     location.setRelativeDescriptor(installLocation.location, m[2]);
  492.   }
  493.   else {
  494.     location.persistentDescriptor = m[2];
  495.   }
  496.  
  497.   return location;
  498. }
  499.  
  500. /**
  501.  * Determines if a file is an item package - either a XPI or a JAR file.
  502.  * @param   file
  503.  *          The file to check
  504.  * @returns true if the file is an item package, false otherwise.
  505.  */
  506. function fileIsItemPackage(file) {
  507.   var fileURL = getURIFromFile(file);
  508.   if (fileURL instanceof nsIURL)
  509.     var extension = fileURL.fileExtension.toLowerCase();
  510.   return extension == "xpi" || extension == "jar";
  511. }
  512.  
  513. /** 
  514.  * Return the leaf name used by the extension system for staging an item.
  515.  * @param   id
  516.  *          The GUID of the item
  517.  * @param   type
  518.  *          The nsIUpdateItem type of the item
  519.  * @returns The leaf name of the staged file.
  520.  */
  521. function getStagedLeafName(id, type) {
  522.   if (type == nsIUpdateItem.TYPE_THEME) 
  523.     return id + ".jar";
  524.   return id + ".xpi";
  525. }
  526.  
  527. /**
  528.  * Opens a safe file output stream for writing. 
  529.  * @param   file
  530.  *          The file to write to.
  531.  * @param   modeFlags
  532.  *          (optional) File open flags. Can be undefined. 
  533.  * @returns nsIFileOutputStream to write to.
  534.  */
  535. function openSafeFileOutputStream(file, modeFlags) {
  536.   var fos = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
  537.                       .createInstance(Components.interfaces.nsIFileOutputStream);
  538.   if (modeFlags === undefined)
  539.     modeFlags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE;
  540.   if (!file.exists()) 
  541.     file.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  542.   fos.init(file, modeFlags, PERMS_FILE, 0);
  543.   return fos;
  544. }
  545.  
  546. /**
  547.  * Closes a safe file output stream.
  548.  * @param   stream
  549.  *          The stream to close.
  550.  */
  551. function closeSafeFileOutputStream(stream) {
  552.   if (stream instanceof Components.interfaces.nsISafeOutputStream)
  553.     stream.finish();
  554.   else
  555.     stream.close();
  556. }
  557.  
  558. /**
  559.  * Deletes a directory and its children. First it tries nsIFile::Remove(true).
  560.  * If that fails it will fall back to recursing, setting the appropriate
  561.  * permissions, and deleting the current entry. This is needed for when we have
  562.  * rights to delete a directory but there are entries that have a read-only
  563.  * attribute (e.g. a copy restore from a read-only CD, etc.)
  564.  * @param   dir
  565.  *          A nsIFile for the directory to be deleted
  566.  */
  567. function removeDirRecursive(dir) {
  568.   try {
  569.     dir.remove(true);
  570.     return;
  571.   }
  572.   catch (e) {
  573.   }
  574.  
  575.   var dirEntries = dir.directoryEntries;
  576.   while (dirEntries.hasMoreElements()) {
  577.     var entry = dirEntries.getNext().QueryInterface(Components.interfaces.nsIFile);
  578.  
  579.     if (entry.isDirectory()) {
  580.       removeDirRecursive(entry);
  581.     }
  582.     else {
  583.       entry.permissions = PERMS_FILE;
  584.       entry.remove(false);
  585.     }
  586.   }
  587.   dir.permissions = PERMS_DIRECTORY;
  588.   dir.remove(true);
  589. }
  590.  
  591. /**
  592.  * Logs a string to the error console. 
  593.  * @param   string
  594.  *          The string to write to the error console..
  595.  */  
  596. function LOG(string) {
  597.   if (gLoggingEnabled)  
  598.     dump("*** " + string + "\n");
  599.   gConsole.logStringMessage(string);
  600. }
  601.  
  602. /** 
  603.  * Randomize the specified file name. Used to force RDF to bypass the cache
  604.  * when loading certain types of files.
  605.  * @param   fileName 
  606.  *          A file name to randomize, e.g. install.rdf
  607.  * @returns A randomized file name, e.g. install-xyz.rdf
  608.  */
  609. function getRandomFileName(fileName) {
  610.   var extensionDelimiter = fileName.lastIndexOf(".");
  611.   var prefix = fileName.substr(0, extensionDelimiter);
  612.   var suffix = fileName.substr(extensionDelimiter);
  613.   
  614.   var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
  615.   var nameString = prefix + "-";
  616.   for (var i = 0; i < 3; ++i) {
  617.     var index = Math.round((Math.random()) * characters.length);
  618.     nameString += characters.charAt(index);
  619.   }
  620.   return nameString + "." + suffix;
  621. }
  622.  
  623. /**
  624.  * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used
  625.  * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource 
  626.  * are NOT prefixed.
  627.  * @param   type
  628.  *          The nsIUpdateItem type to find a RDF URI prefix for
  629.  * @returns The RDF URI prefix.
  630.  */
  631. function getItemPrefix(type) {
  632.   if (type & nsIUpdateItem.TYPE_EXTENSION) 
  633.     return PREFIX_EXTENSION;
  634.   else if (type & nsIUpdateItem.TYPE_THEME)
  635.     return PREFIX_THEME;
  636.   return PREFIX_ITEM_URI;
  637. }
  638.  
  639. /**
  640.  * Trims a prefix from a string.
  641.  * @param   string
  642.  *          The source string
  643.  * @param   prefix
  644.  *          The prefix to remove.
  645.  * @returns The suffix (string - prefix)
  646.  */
  647. function stripPrefix(string, prefix) {
  648.   return string.substr(prefix.length);
  649. }
  650.  
  651. /**
  652.  * Gets a File URL spec for a nsIFile
  653.  * @param   file
  654.  *          The file to get a file URL spec to
  655.  * @returns The file URL spec to the file
  656.  */
  657. function getURLSpecFromFile(file) {
  658.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  659.                          .getService(Components.interfaces.nsIIOService);
  660.   var fph = ioServ.getProtocolHandler("file")
  661.                   .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  662.   return fph.getURLSpecFromFile(file);
  663. }
  664.  
  665. /**
  666.  * Constructs a URI to a spec.
  667.  * @param   spec
  668.  *          The spec to construct a URI to
  669.  * @returns The nsIURI constructed.
  670.  */
  671. function newURI(spec) {
  672.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  673.                          .getService(Components.interfaces.nsIIOService);
  674.   return ioServ.newURI(spec, null, null);
  675. }
  676.  
  677. /** 
  678.  * Constructs a File URI to a nsIFile
  679.  * @param   file
  680.  *          The file to construct a File URI to
  681.  * @returns The file URI to the file
  682.  */
  683. function getURIFromFile(file) {
  684.   var ioServ = Components.classes["@mozilla.org/network/io-service;1"]
  685.                          .getService(Components.interfaces.nsIIOService);
  686.   return ioServ.newFileURI(file);
  687. }
  688.  
  689. /**
  690.  * @returns Whether or not we are currently running in safe mode.
  691.  */
  692. function inSafeMode() {
  693.   return gApp.inSafeMode;
  694. }
  695.  
  696. /**
  697.  * Extract the string value from a RDF Literal or Resource
  698.  * @param   literalOrResource
  699.  *          RDF String Literal or Resource
  700.  * @returns String value of the literal or resource, or undefined if the object
  701.  *          supplied is not a RDF string literal or resource.
  702.  */
  703. function stringData(literalOrResource) {
  704.   if (literalOrResource instanceof Components.interfaces.nsIRDFLiteral)
  705.     return literalOrResource.Value;
  706.   if (literalOrResource instanceof Components.interfaces.nsIRDFResource)
  707.     return literalOrResource.Value;
  708.   return undefined;
  709. }
  710.  
  711. /**
  712.  * Extract the integer value of a RDF Literal
  713.  * @param   literal
  714.  *          nsIRDFInt literal
  715.  * @return  integer value of the literal
  716.  */
  717. function intData(literal) {
  718.   if (literal instanceof Components.interfaces.nsIRDFInt)
  719.     return literal.Value;
  720.   return undefined;
  721. }
  722.  
  723. /**
  724.  * Gets a property from an install manifest.
  725.  * @param   installManifest
  726.  *          An Install Manifest datasource to read from
  727.  * @param   property
  728.  *          The name of a proprety to read (sans EM_NS)
  729.  * @returns The literal value of the property, or undefined if the property has
  730.  *          no value.
  731.  */
  732. function getManifestProperty(installManifest, property) {
  733.   var target = installManifest.GetTarget(gInstallManifestRoot, 
  734.                                          gRDF.GetResource(EM_NS(property)), true);
  735.   var val = stringData(target);
  736.   return val === undefined ? intData(target) : val;
  737. }
  738.  
  739. /**
  740.  * Given an Install Manifest Datasource, retrieves the type of item the manifest
  741.  * describes.
  742.  * @param   installManifest 
  743.  *          The Install Manifest Datasource.
  744.  * @return  The nsIUpdateItem type of the item described by the manifest
  745.  *          returns TYPE_EXTENSION if attempts to determine the type fail.
  746.  */
  747. function getAddonTypeFromInstallManifest(installManifest) {
  748.   var typeArc = gRDF.GetResource(EM_NS("type"));
  749.   var type = intData(installManifest.GetTarget(gInstallManifestRoot, typeArc, true));
  750.   if (type !== undefined)
  751.     return type;
  752.  
  753.   // Firefox 1.0 and earlier did not support addon-type annotation on the 
  754.   // Install Manifest, so we fall back to a theme-only property to 
  755.   // differentiate.
  756.   if (getManifestProperty(installManifest, "internalName") !== undefined)
  757.     return nsIUpdateItem.TYPE_THEME;
  758.  
  759.   // If no type is provided, default to "Extension"
  760.   return nsIUpdateItem.TYPE_EXTENSION;    
  761. }
  762.  
  763. /**
  764.  * Shows a message about an incompatible Extension/Theme. 
  765.  * @param   installData
  766.  *          An Install Data object from |getInstallData|
  767.  */
  768. function showIncompatibleError(installData) {
  769.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  770.   var params = [extensionStrings.GetStringFromName("type-" + installData.type)];
  771.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  772.                                                     params, params.length);
  773.   var message;
  774.   var targetAppData = installData.currentApp;
  775.   if (!targetAppData) {
  776.     params = [installData.name, installData.version, BundleManager.appName];
  777.     message = extensionStrings.formatStringFromName("incompatibleMessageNoApp", 
  778.                                                     params, params.length);
  779.   }
  780.   else if (targetAppData.minVersion == targetAppData.maxVersion) {
  781.     // If the min target app version and the max target app version are the same, don't show
  782.     // a message like, "Foo is only compatible with Firefox versions 0.7 to 0.7", rather just
  783.     // show, "Foo is only compatible with Firefox 0.7"
  784.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  785.               installData.name, installData.version, BundleManager.appName, 
  786.               targetAppData.minVersion];
  787.     message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion", 
  788.                                                     params, params.length);
  789.   }
  790.   else {
  791.     params = [installData.name, installData.version, BundleManager.appName, gApp.version, 
  792.               installData.name, installData.version, BundleManager.appName, 
  793.               targetAppData.minVersion, targetAppData.maxVersion];
  794.     message = extensionStrings.formatStringFromName("incompatibleMsg", params, params.length);
  795.   }
  796.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  797.                      .getService(Components.interfaces.nsIPromptService);
  798.   ps.alert(null, title, message);
  799. }
  800.  
  801. /**
  802.  * Shows a message.
  803.  * @param   titleKey
  804.  *          String key of the title string in the Extensions localization file.
  805.  * @param   messageKey
  806.  *          String key of the message string in the Extensions localization file.
  807.  * @param   messageParams
  808.  *          Array of strings to be substituted into |messageKey|. Can be null.
  809.  */
  810. function showMessage(titleKey, titleParams, messageKey, messageParams) {
  811.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  812.   if (titleParams && titleParams.length > 0) {
  813.     var title = extensionStrings.formatStringFromName(titleKey, titleParams,
  814.                                                       titleParams.length);
  815.   }
  816.   else
  817.     title = extensionStrings.GetStringFromName(titleKey);
  818.  
  819.   if (messageParams && messageParams.length > 0) {
  820.     var message = extensionStrings.formatStringFromName(messageKey, messageParams,
  821.                                                         messageParams.length);
  822.   }
  823.   else
  824.     message = extensionStrings.GetStringFromName(messageKey);
  825.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  826.                      .getService(Components.interfaces.nsIPromptService);
  827.   ps.alert(null, title, message);
  828. }
  829.  
  830. /** 
  831.  * Gets a zip reader for the file specified.
  832.  * @param   zipFile
  833.  *          A ZIP archive to open with a nsIZipReader.
  834.  * @return  A nsIZipReader for the file specified.
  835.  */
  836. function getZipReaderForFile(zipFile) {
  837.   try {
  838.     var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  839.                               .createInstance(Components.interfaces.nsIZipReader);
  840.     zipReader.init(zipFile);
  841.     zipReader.open();
  842.   }
  843.   catch (e) {
  844.     zipReader.close();
  845.     throw e;
  846.   }
  847.   return zipReader;
  848. }
  849.  
  850. /** 
  851.  * Extract a RDF file from a ZIP archive to a random location in the system
  852.  * temp directory.
  853.  * @param   zipFile
  854.  *          A ZIP archive to read from
  855.  * @param   fileName 
  856.  *          The name of the file to read from the zip. 
  857.  * @param   suppressErrors
  858.  *          Whether or not to report errors. 
  859.  * @return  The file created in the temp directory.
  860.  */
  861. function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) {
  862.   var file = getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]);
  863.   try {
  864.     var zipReader = getZipReaderForFile(zipFile);
  865.     zipReader.getEntry(fileName);
  866.     zipReader.extract(fileName, file);
  867.     zipReader.close();
  868.   }
  869.   catch (e) {
  870.     if (!suppressErrors) {
  871.       showMessage("missingFileTitle", [], "missingFileMessage", 
  872.                   [BundleManager.appName, fileName]);
  873.       throw e;
  874.     }
  875.   }
  876.   return file;
  877. }
  878.  
  879. /**
  880.  * Show a message to the user informing them they are installing an old non-EM
  881.  * style Theme, and that these are not supported.
  882.  * @param   installManifest 
  883.  *          The Old-Style Contents Manifest datasource representing the theme. 
  884.  */
  885. function showOldThemeError(contentsManifest) {
  886.   var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  887.   var params = [extensionStrings.GetStringFromName("theme")];
  888.   var title = extensionStrings.formatStringFromName("incompatibleTitle", 
  889.                                                     params, params.length);
  890.   var appVersion = extensionStrings.GetStringFromName("incompatibleOlder");
  891.   
  892.   try {  
  893.     var ctr = getContainer(contentsManifest, 
  894.                            gRDF.GetResource("urn:mozilla:skin:root"));
  895.     var elts = ctr.GetElements();
  896.     var nameArc = gRDF.GetResource(CHROME_NS("displayName"));
  897.     while (elts.hasMoreElements()) {
  898.       var elt = elts.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  899.       themeName = stringData(contentsManifest.GetTarget(elt, nameArc, true));
  900.       if (themeName) 
  901.         break;
  902.     }
  903.   }
  904.   catch (e) {
  905.     themeName = extensionStrings.GetStringFromName("incompatibleThemeName");
  906.   }
  907.   
  908.   params = [themeName, "", BundleManager.appName, gApp.version, themeName, "", 
  909.             BundleManager.appName, appVersion];
  910.   var message = extensionStrings.formatStringFromName("incompatibleMsgSingleAppVersion",
  911.                                                       params, params.length);
  912.   var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  913.                      .getService(Components.interfaces.nsIPromptService);
  914.   ps.alert(null, title, message);
  915. }
  916.  
  917. /**
  918.  * Gets an Install Manifest datasource from a file.
  919.  * @param   file
  920.  *          The nsIFile that contains the Install Manifest RDF
  921.  * @returns The Install Manifest datasource
  922.  */
  923. function getInstallManifest(file) {
  924.   var fileURL = getURLSpecFromFile(file);
  925.   var ds = gRDF.GetDataSourceBlocking(fileURL);
  926.   var arcs = ds.ArcLabelsOut(gInstallManifestRoot);
  927.   if (!arcs.hasMoreElements()) {
  928.     ds = null;
  929.     var uri = Components.classes["@mozilla.org/network/io-service;1"]
  930.                         .getService(Components.interfaces.nsIIOService)
  931.                         .newFileURI(file);
  932.     var url = uri.QueryInterface(nsIURL);
  933.     showMessage("malformedTitle", [], "malformedMessage", 
  934.                 [BundleManager.appName, url.fileName]);
  935.   }
  936.   return ds;
  937. }
  938.  
  939. /**
  940.  * An enumeration of items in a JS array.
  941.  * @constructor
  942.  */
  943. function ArrayEnumerator(aItems) {
  944.   this._index = 0;
  945.   if (aItems) {
  946.     for (var i = 0; i < aItems.length; ++i) {
  947.       if (!aItems[i])
  948.         aItems.splice(i, 1);      
  949.     }
  950.   }
  951.   this._contents = aItems;
  952. }
  953.  
  954. ArrayEnumerator.prototype = {
  955.   _index: 0,
  956.   _contents: [],
  957.   
  958.   hasMoreElements: function() {
  959.     return this._index < this._contents.length;
  960.   },
  961.   
  962.   getNext: function() {
  963.     return this._contents[this._index++];      
  964.   }
  965. };
  966.  
  967. /**
  968.  * An enumeration of files in a JS array.
  969.  * @param   files
  970.  *          The files to enumerate
  971.  * @constructor
  972.  */
  973. function FileEnumerator(files) {
  974.   this._index = 0;
  975.   if (files) {
  976.     for (var i = 0; i < files.length; ++i) {
  977.       if (!files[i])
  978.         files.splice(i, 1);      
  979.     }
  980.   }
  981.   this._contents = files;
  982. }
  983.  
  984. FileEnumerator.prototype = {
  985.   _index: 0,
  986.   _contents: [],
  987.  
  988.   /**
  989.    * Gets the next file in the sequence.
  990.    */  
  991.   get nextFile() {
  992.     if (this._index < this._contents.length)
  993.       return this._contents[this._index++];
  994.     return null;
  995.   },
  996.   
  997.   /**
  998.    * Stop enumerating. Nothing to do here.
  999.    */
  1000.   close: function() {
  1001.   },
  1002. };
  1003.  
  1004. /**
  1005.  * An object which identifies an Install Location for items, where the location
  1006.  * relationship is each item living in a directory named with its GUID under 
  1007.  * the directory used when constructing this object.
  1008.  *
  1009.  * e.g. <location>\{GUID1}
  1010.  *      <location>\{GUID2}
  1011.  *      <location>\{GUID3}
  1012.  *      ...
  1013.  *
  1014.  * @param   name
  1015.  *          The string identifier of this Install Location.
  1016.  * @param   location
  1017.  *          The directory that contains the items. 
  1018.  * @constructor
  1019.  */
  1020. function DirectoryInstallLocation(name, location, restricted, priority) {
  1021.   this._name = name;
  1022.   if (location.exists()) {
  1023.     if (!location.isDirectory())
  1024.       throw new Error("location must be a directoy!");
  1025.   }
  1026.   else {
  1027.     try {
  1028.       location.create(nsILocalFile.DIRECTORY_TYPE, 0775);
  1029.     }
  1030.     catch (e) {
  1031.       LOG("DirectoryInstallLocation: failed to create location " + 
  1032.           " directory = " + location.path + ", exception = " + e + "\n");
  1033.     }
  1034.   }
  1035.  
  1036.   this._location = location;
  1037.   this._locationToIDMap = {};
  1038.   this._restricted = restricted;
  1039.   this._priority = priority;
  1040. }
  1041. DirectoryInstallLocation.prototype = {
  1042.   _name           : "",
  1043.   _location       : null,
  1044.   _locationToIDMap: null,
  1045.   _restricted     : false,
  1046.   _priority       : 0,
  1047.   _canAccess      : null,
  1048.   
  1049.   /**
  1050.    * See nsIExtensionManager.idl
  1051.    */
  1052.   get name() {
  1053.     return this._name;
  1054.   },
  1055.   
  1056.   /**
  1057.    * Reads a directory linked to in a file.
  1058.    * @param   file
  1059.    *          The file containing the directory path
  1060.    * @returns A nsILocalFile object representing the linked directory.
  1061.    */
  1062.   _readDirectoryFromFile: function(file) {
  1063.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  1064.                         .createInstance(Components.interfaces.nsIFileInputStream);
  1065.     fis.init(file, -1, -1, false);
  1066.     var line = { value: "" };
  1067.     if (fis instanceof nsILineInputStream)
  1068.       fis.readLine(line);
  1069.     fis.close();
  1070.     if (line.value) {
  1071.       var linkedDirectory = Components.classes["@mozilla.org/file/local;1"]
  1072.                                       .createInstance(nsILocalFile);
  1073.       try {
  1074.         linkedDirectory.initWithPath(line.value);
  1075.       }
  1076.       catch (e) {
  1077.         linkedDirectory.setRelativeDescriptor(file.parent, line.value);
  1078.       }
  1079.       
  1080.       return linkedDirectory;
  1081.     }
  1082.     return null;
  1083.   },
  1084.   
  1085.   /**
  1086.    * See nsIExtensionManager.idl
  1087.    */
  1088.   get itemLocations() {
  1089.     var locations = [];
  1090.     if (!this._location.exists())
  1091.       return new FileEnumerator(locations);
  1092.     
  1093.     try {
  1094.       var entries = this._location.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1095.       while (true) {
  1096.         var entry = entries.nextFile;
  1097.         if (!entry)
  1098.           break;
  1099.         entry instanceof nsILocalFile;
  1100.         if (!entry.isDirectory() && gIDTest.test(entry.leafName)) {
  1101.           var linkedDirectory = this._readDirectoryFromFile(entry);
  1102.           if (linkedDirectory && linkedDirectory.exists() && 
  1103.               linkedDirectory.isDirectory()) {
  1104.             locations.push(linkedDirectory);
  1105.             this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName;
  1106.           }
  1107.         }
  1108.         else
  1109.           locations.push(entry);
  1110.       }
  1111.       entries.close();
  1112.     }
  1113.     catch (e) { 
  1114.     }
  1115.     return new FileEnumerator(locations);
  1116.   },
  1117.   
  1118.   /**
  1119.    * Retrieves the GUID for an item at the specified location.
  1120.    * @param   file
  1121.    *          The location where an item might live.
  1122.    * @returns The ID for an item that might live at the location specified.
  1123.    * 
  1124.    * N.B. This function makes no promises about whether or not this path is 
  1125.    *      actually maintained by this Install Location.
  1126.    */
  1127.   getIDForLocation: function(file) {
  1128.     var section = file.leafName;
  1129.     var filePD = file.persistentDescriptor;
  1130.     if (filePD in this._locationToIDMap) 
  1131.       section = this._locationToIDMap[filePD];
  1132.     
  1133.     if (gIDTest.test(section))
  1134.       return RegExp.$1;
  1135.     return undefined;
  1136.   },
  1137.   
  1138.   /**
  1139.    * See nsIExtensionManager.idl
  1140.    */
  1141.   get location() {
  1142.     return this._location.clone();
  1143.   },
  1144.   
  1145.   /**
  1146.    * See nsIExtensionManager.idl
  1147.    */
  1148.   get restricted() {
  1149.     return this._restricted;
  1150.   },
  1151.   
  1152.   /**
  1153.    * See nsIExtensionManager.idl
  1154.    */
  1155.   get canAccess() {
  1156.     if (this._canAccess != null)
  1157.       return this._canAccess;
  1158.  
  1159.     var testFile = this.location;
  1160.     testFile.append("Access Privileges Test");
  1161.     try {
  1162.       testFile.createUnique(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1163.       testFile.remove(false);
  1164.       this._canAccess = true;
  1165.     }
  1166.     catch (e) {
  1167.       this._canAccess = false;
  1168.     }
  1169.     return this._canAccess;
  1170.   },
  1171.   
  1172.   /**
  1173.    * See nsIExtensionManager.idl
  1174.    */
  1175.   get priority() {
  1176.     return this._priority;
  1177.   },
  1178.   
  1179.   /**
  1180.    * See nsIExtensionManager.idl
  1181.    */
  1182.   getItemLocation: function(id) {
  1183.     var itemLocation = this.location;
  1184.     itemLocation.append(id);
  1185.     if (itemLocation.exists() && !itemLocation.isDirectory())
  1186.       return this._readDirectoryFromFile(itemLocation);
  1187.     if (!itemLocation.exists() && this.canAccess)
  1188.       itemLocation.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1189.     return itemLocation;
  1190.   },
  1191.   
  1192.   /**
  1193.    * See nsIExtensionManager.idl
  1194.    */
  1195.   itemIsManagedIndependently: function(id) {
  1196.     var itemLocation = this.location;
  1197.     itemLocation.append(id);
  1198.     return itemLocation.exists() && !itemLocation.isDirectory();      
  1199.   },
  1200.   
  1201.   /**
  1202.    * See nsIExtensionManager.idl
  1203.    */
  1204.   getItemFile: function(id, filePath) {
  1205.     var itemLocation = this.getItemLocation(id).clone();
  1206.     var parts = filePath.split("/");
  1207.     for (var i = 0; i < parts.length; ++i)
  1208.       itemLocation.append(parts[i]);
  1209.     return itemLocation;
  1210.   },
  1211.  
  1212.   /**
  1213.    * Stages the specified file for later.
  1214.    * @param   file
  1215.    *          The file to stage
  1216.    * @param   id
  1217.    *          The GUID of the item the file represents
  1218.    */
  1219.   stageFile: function(file, id) {
  1220.     var stagedFile = this.location;
  1221.     stagedFile.append(DIR_STAGE);
  1222.     stagedFile.append(id);
  1223.     stagedFile.append(file.leafName);
  1224.  
  1225.     // When an incompatible update is successful the file is already staged
  1226.     if (stagedFile.equals(file))
  1227.       return stagedFile;
  1228.  
  1229.     if (stagedFile.exists()) 
  1230.       stagedFile.remove(false);
  1231.       
  1232.     file.copyTo(stagedFile.parent, stagedFile.leafName);
  1233.     
  1234.     // If the file has incorrect permissions set, correct them now.
  1235.     if (!stagedFile.isWritable())
  1236.       stagedFile.permissions = PERMS_FILE;
  1237.     
  1238.     return stagedFile;
  1239.   },
  1240.   
  1241.   /**
  1242.    * Returns the most recently staged package (e.g. the last XPI or JAR in a
  1243.    * directory) for an item and removes items that do not qualify.
  1244.    * @param   id
  1245.    *          The ID of the staged package
  1246.    * @returns an nsIFile if the package exists otherwise null.
  1247.    */
  1248.   getStageFile: function(id) {
  1249.     var stageFile = null;
  1250.     var stageDir = this.location;
  1251.     stageDir.append(DIR_STAGE);
  1252.     stageDir.append(id);
  1253.     if (!stageDir.exists() || !stageDir.isDirectory())
  1254.       return null;
  1255.     try {
  1256.       var entries = stageDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1257.       while (entries.hasMoreElements()) {
  1258.         var file = entries.nextFile;
  1259.         if (!(file instanceof nsILocalFile))
  1260.           continue;
  1261.         if (file.isDirectory())
  1262.           removeDirRecursive(file);
  1263.         else if (fileIsItemPackage(file)) {
  1264.           if (stageFile)
  1265.             stageFile.remove(false);
  1266.           stageFile = file;
  1267.         }
  1268.         else
  1269.           file.remove(false);
  1270.       }
  1271.     }
  1272.     catch (e) {
  1273.     }
  1274.     if (entries instanceof nsIDirectoryEnumerator)
  1275.       entries.close();
  1276.     return stageFile;
  1277.   },
  1278.   
  1279.   /**
  1280.    * Removes a file from the stage. This cleans up the stage if there is nothing
  1281.    * else left after the remove operation.
  1282.    * @param   file
  1283.    *          The file to remove.
  1284.    */
  1285.   removeFile: function(file) {
  1286.     if (file.exists())
  1287.       file.remove(false);
  1288.     var parent = file.parent;
  1289.     var entries = parent.directoryEntries;    
  1290.     try {
  1291.       // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
  1292.       // it has been removed will cause a crash on Mac OS X - bug 292823
  1293.       while (parent && !parent.equals(this.location) &&
  1294.             !entries.hasMoreElements()) {
  1295.         parent.remove(false);
  1296.         parent = parent.parent;
  1297.         entries = parent.directoryEntries;
  1298.       }
  1299.       if (entries instanceof nsIDirectoryEnumerator)
  1300.         entries.close();
  1301.     }
  1302.     catch (e) {
  1303.       LOG("DirectoryInstallLocation::removeFile: failed to remove staged " + 
  1304.           " directory = " + parent.path + ", exception = " + e + "\n");
  1305.     }
  1306.   },
  1307.   
  1308.   /**
  1309.    * See nsISupports.idl
  1310.    */
  1311.   QueryInterface: function (iid) {
  1312.     if (!iid.equals(Components.interfaces.nsIInstallLocation) &&
  1313.         !iid.equals(Components.interfaces.nsISupports))
  1314.       throw Components.results.NS_ERROR_NO_INTERFACE;
  1315.     return this;
  1316.   }
  1317. };
  1318.  
  1319. //@line 1391 "/build/buildd/mozilla-thunderbird-1.5.0.10/build-dir/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  1320.  
  1321. /**
  1322.  * An object which handles the installation of an Extension.
  1323.  * @constructor
  1324.  */
  1325. function Installer(ds, id, installLocation, type) {
  1326.   this._ds = ds;
  1327.   this._id = id;
  1328.   this._type = type;
  1329.   this._installLocation = installLocation;
  1330.   this._metadataFile = this._installLocation
  1331.                            .getItemFile(this._id, FILE_INSTALL_MANIFEST);
  1332. }
  1333. Installer.prototype = {
  1334.   // Item metadata
  1335.   _id: null,
  1336.   _ds: null,
  1337.   _installLocation: null,
  1338.   _metadataDS: null,
  1339.   
  1340.   /**
  1341.    * Gets the Install Manifest datasource we are installing from.
  1342.    */
  1343.   get metadataDS() {
  1344.     if (!this._metadataDS) {
  1345.       if (!this._metadataFile.exists()) 
  1346.         return null;
  1347.       this._metadataDS = getInstallManifest(this._metadataFile);
  1348.       if (!this._metadataDS) {
  1349.         LOG("Installer::install: metadata datasource for extension " + 
  1350.             this._id + " at " + this._metadataFile.path + " could not be loaded. " + 
  1351.             " Installation will not proceed.");
  1352.       }
  1353.     }
  1354.     return this._metadataDS;
  1355.   },
  1356.   
  1357.   /**
  1358.    * Installs the Extension
  1359.    * @param   file
  1360.    *          A XPI/JAR file to install from. If this is null or does not exist,
  1361.    *          the item is assumed to be an expanded directory, located at the GUID
  1362.    *          key in the supplied Install Location.
  1363.    */
  1364.   installFromFile: function(file) {
  1365.     // Move files from the staging dir into the extension's final home.
  1366.     if (file && file.exists()) {
  1367.       this._installExtensionFiles(file);
  1368.     }
  1369.  
  1370.     if (!this.metadataDS)
  1371.       return;
  1372.  
  1373.     // Upgrade old-style contents.rdf Chrome Manifests if necessary.
  1374.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1375.       this.upgradeThemeChrome();
  1376.     else
  1377.       this.upgradeExtensionChrome();
  1378.  
  1379.     // Add metadata for the extension to the global extension metadata set
  1380.     this._ds.addItemMetadata(this._id, this.metadataDS, this._installLocation);
  1381.   },
  1382.   
  1383.   /**
  1384.    * Safely extract the Extension's files into the target folder.
  1385.    * @param   file
  1386.    *          The XPI/JAR file to install from.
  1387.    */
  1388.   _installExtensionFiles: function(file) {
  1389.     var installer = this;
  1390.     /**
  1391.       * Callback for |safeInstallOperation| that performs file level installation
  1392.       * steps for an Extension.
  1393.       * @param   extensionID
  1394.       *          The GUID of the Extension being installed.
  1395.       * @param   installLocation 
  1396.       *          The Install Location where the Extension is being installed.
  1397.       * @param   xpiFile
  1398.       *          The source XPI file that contains the Extension.
  1399.       */
  1400.     function extractExtensionFiles(extensionID, installLocation, xpiFile) {
  1401.       // Create a logger to log install operations for uninstall. This must be 
  1402.       // created in the |safeInstallOperation| callback, since it creates a file
  1403.       // in the target directory. If we do this outside of the callback, we may
  1404.       // be clobbering a file we should not be.
  1405.       var zipReader = getZipReaderForFile(xpiFile);
  1406.       
  1407.       // create directories first
  1408.       var entries = zipReader.findEntries("*/");
  1409.       while (entries.hasMoreElements()) {
  1410.         var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1411.         var target = installLocation.getItemFile(extensionID, entry.name);
  1412.         if (!target.exists()) {
  1413.           try {
  1414.             target.create(nsILocalFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  1415.           }
  1416.           catch (e) {
  1417.             LOG("extractExtensionsFiles: failed to create target directory for extraction " + 
  1418.                 " file = " + target.path + ", exception = " + e + "\n");
  1419.           }
  1420.         }
  1421.       }
  1422.  
  1423.       entries = zipReader.findEntries("*");
  1424.       while (entries.hasMoreElements()) {
  1425.         entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1426.         target = installLocation.getItemFile(extensionID, entry.name);
  1427.         if (target.exists())
  1428.           continue;
  1429.  
  1430.         try {
  1431.           target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1432.         }
  1433.         catch (e) {
  1434.           LOG("extractExtensionsFiles: failed to create target file for extraction " + 
  1435.               " file = " + target.path + ", exception = " + e + "\n");
  1436.         }
  1437.         zipReader.extract(entry.name, target);
  1438.       }
  1439.       zipReader.close();
  1440.     }
  1441.  
  1442.     var installer = this;
  1443.     /**
  1444.       * Callback for |safeInstallOperation| that performs file level installation
  1445.       * steps for a Theme.
  1446.       * @param   id
  1447.       *          The GUID of the Theme being installed.
  1448.       * @param   installLocation 
  1449.       *          The Install Location where the Theme is being installed.
  1450.       * @param   jarFile
  1451.       *          The source JAR file that contains the Theme.
  1452.       */
  1453.     function extractThemeFiles(id, installLocation, jarFile) {
  1454.       var themeDirectory = installLocation.getItemLocation(id);
  1455.       var zipReader = getZipReaderForFile(jarFile);
  1456.  
  1457.       // The only critical file is the install.rdf and we would not have
  1458.       // gotten this far without one.
  1459.       var rootFiles = [FILE_INSTALL_MANIFEST, FILE_CHROME_MANIFEST,
  1460.                        "preview.png", "icon.png"];
  1461.       for (var i = 0; i < rootFiles.length; ++i) {
  1462.         try {
  1463.           var entry = zipReader.getEntry(rootFiles[i]);
  1464.           var target = installLocation.getItemFile(id, rootFiles[i]);
  1465.           zipReader.extract(rootFiles[i], target);
  1466.         }
  1467.         catch (e) {
  1468.         }
  1469.       }
  1470.  
  1471.       var manifestFile = installLocation.getItemFile(id, FILE_CHROME_MANIFEST);
  1472.       // new theme structure requires a chrome.manifest file
  1473.       if (manifestFile.exists()) {
  1474.         var entries = zipReader.findEntries(DIR_CHROME + "/*");
  1475.         while (entries.hasMoreElements()) {
  1476.           entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  1477.           if (entry.name.substr(entry.name.length - 1, 1) == "/")
  1478.             continue;
  1479.           target = installLocation.getItemFile(id, entry.name);
  1480.           try {
  1481.             target.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1482.           }
  1483.           catch (e) {
  1484.             LOG("extractThemeFiles: failed to create target file for extraction " + 
  1485.                 " file = " + target.path + ", exception = " + e + "\n");
  1486.           }
  1487.           zipReader.extract(entry.name, target);
  1488.         }
  1489.         zipReader.close();
  1490.       }
  1491.       else { // old theme structure requires only an install.rdf
  1492.         try {
  1493.           var entry = zipReader.getEntry(FILE_CONTENTS_MANIFEST);
  1494.           var contentsManifestFile = installLocation.getItemFile(id, FILE_CONTENTS_MANIFEST);
  1495.           contentsManifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1496.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  1497.         }
  1498.         catch (e) {
  1499.           zipReader.close();
  1500.           LOG("extractThemeFiles: failed to extract contents.rdf: " + target.path);
  1501.           throw e; // let the safe-op clean up
  1502.         }
  1503.         zipReader.close();
  1504.         var chromeDir = installLocation.getItemFile(id, DIR_CHROME);
  1505.         try {
  1506.           jarFile.copyTo(chromeDir, jarFile.fileName);
  1507.         }
  1508.         catch (e) {
  1509.           LOG("extractThemeFiles: failed to copy theme JAR file to: " + chromeDir.path);
  1510.           throw e; // let the safe-op clean up
  1511.         }
  1512.  
  1513.         if (!installer.metadataDS && installer._type == nsIUpdateItem.TYPE_THEME) {
  1514.           if (contentsManifestFile && contentsManifestFile.exists()) {
  1515.             var contentsManifest = gRDF.GetDataSourceBlocking(getURLSpecFromFile(contentsManifestFile));
  1516.             showOldThemeError(contentsManifest);
  1517.           }
  1518.           LOG("Theme JAR file: " + jarFile.leafName + " contains an Old-Style " + 
  1519.               "Theme that is not compatible with this version of the software.");
  1520.           throw new Error("Old Theme"); // let the safe-op clean up
  1521.         }
  1522.       }
  1523.     }
  1524.  
  1525.     var callback = extractExtensionFiles;
  1526.     if (this._type == nsIUpdateItem.TYPE_THEME)
  1527.       callback = extractThemeFiles;
  1528.     safeInstallOperation(this._id, this._installLocation,
  1529.                           { callback: callback, data: file });
  1530.   },
  1531.   
  1532.   /** 
  1533.    * Upgrade contents.rdf Chrome Manifests used by this Theme to the new 
  1534.    * chrome.manifest format if necessary.
  1535.    */
  1536.   upgradeThemeChrome: function() {
  1537.     // Use the Chrome Registry API to install the theme there
  1538.     var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1539.                        .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1540.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1541.     if (manifestFile.exists() ||
  1542.         this._id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  1543.       return;
  1544.  
  1545.     try {
  1546.       // creates a chrome manifest for themes
  1547.       var manifestURI = getURIFromFile(manifestFile);
  1548.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1549.       // We're relying on the fact that there is only one JAR file
  1550.       // in the "chrome" directory. This is a hack, but it works.
  1551.       var entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1552.       var jarFile = entries.nextFile;
  1553.       if (jarFile) {
  1554.         var jarFileURI = getURIFromFile(jarFile);
  1555.         var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  1556.         var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1557.         var contentsFileURI = getURIFromFile(contentsFile.parent);
  1558.  
  1559.         cr.processContentsManifest(contentsFileURI, manifestURI, contentsURI, false, true);
  1560.       }
  1561.       entries.close();
  1562.       contentsFile.remove(false);
  1563.     }
  1564.     catch (e) {
  1565.       // Failed to register chrome, for any number of reasons - non-existent 
  1566.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1567.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1568.       // extension is uninstalled properly during the subsequent uninstall 
  1569.       // pass in |ExtensionManager::_finalizeOperations|
  1570.       LOG("upgradeThemeChrome: failed for theme " + this._id + " - why " + 
  1571.           "not convert to the new chrome.manifest format while you're at it? " + 
  1572.           "Failure exception: " + e);
  1573.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1574.                   [BundleManager.appName]);
  1575.  
  1576.       var stageFile = this._installLocation.getStageFile(this._id);
  1577.       if (stageFile)
  1578.         this._installLocation.removeFile(stageFile);
  1579.  
  1580.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1581.       StartupCache.write();
  1582.     }
  1583.   },
  1584.  
  1585.   /** 
  1586.    * Upgrade contents.rdf Chrome Manifests used by this Extension to the new 
  1587.    * chrome.manifest format if necessary.
  1588.    */
  1589.   upgradeExtensionChrome: function() {
  1590.     // If the extension is aware of the new flat chrome manifests and has 
  1591.     // included one, just use it instead of generating one from the
  1592.     // install.rdf/contents.rdf data.
  1593.     var manifestFile = this._installLocation.getItemFile(this._id, FILE_CHROME_MANIFEST);
  1594.     if (manifestFile.exists())
  1595.       return;
  1596.  
  1597.     try {
  1598.       // Enumerate the metadata datasource files collection and register chrome
  1599.       // for each file, calling _registerChrome for each.
  1600.       var chromeDir = this._installLocation.getItemFile(this._id, DIR_CHROME);
  1601.       
  1602.       if (!manifestFile.parent.exists())
  1603.         return;
  1604.  
  1605.       // Even if an extension doesn't have any chrome, we generate an empty
  1606.       // manifest file so that we don't try to upgrade from the "old-style"
  1607.       // chrome manifests at every startup.
  1608.       manifestFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1609.  
  1610.       var manifestURI = getURIFromFile(manifestFile);
  1611.       var files = this.metadataDS.GetTargets(gInstallManifestRoot, EM_R("file"), true);
  1612.       while (files.hasMoreElements()) {
  1613.         var file = files.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  1614.         var chromeFile = chromeDir.clone();
  1615.         var fileName = file.Value.substr("urn:mozilla:extension:file:".length, file.Value.length);
  1616.         chromeFile.append(fileName);
  1617.  
  1618.         var fileURLSpec = getURLSpecFromFile(chromeFile);
  1619.         if (!chromeFile.isDirectory()) {
  1620.           var zipReader = getZipReaderForFile(chromeFile);
  1621.           fileURLSpec = "jar:" + fileURLSpec + "!/";
  1622.           var contentsFile = this._installLocation.getItemFile(this._id, FILE_CONTENTS_MANIFEST);
  1623.           contentsFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  1624.         }
  1625.  
  1626.         var providers = [EM_R("package"), EM_R("skin"), EM_R("locale")];
  1627.         for (var i = 0; i < providers.length; ++i) {
  1628.           var items = this.metadataDS.GetTargets(file, providers[i], true);
  1629.           while (items.hasMoreElements()) {
  1630.             var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFLiteral);
  1631.             var fileURI = newURI(fileURLSpec + item.Value);
  1632.             // Extract the contents.rdf files instead of opening them inside of
  1633.             // the jar. This prevents the jar from being cached by the zip
  1634.             // reader which will keep the jar in use and prevent deletion.
  1635.             if (zipReader) {
  1636.               zipReader.extract(item.Value + FILE_CONTENTS_MANIFEST, contentsFile);
  1637.               var contentsFileURI = getURIFromFile(contentsFile.parent);
  1638.             }
  1639.             else
  1640.               contentsFileURI = fileURI;
  1641.  
  1642.             var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  1643.                                .getService(Components.interfaces.nsIToolkitChromeRegistry);
  1644.             cr.processContentsManifest(contentsFileURI, manifestURI, fileURI, true, false);
  1645.           }
  1646.         }
  1647.         if (zipReader) {
  1648.           zipReader.close();
  1649.           zipReader = null;
  1650.           contentsFile.remove(false);
  1651.         }
  1652.       }
  1653.     }
  1654.     catch (e) {
  1655.       // Failed to register chrome, for any number of reasons - non-existent 
  1656.       // contents.rdf file at the location specified, malformed contents.rdf, 
  1657.       // etc. Set the pending op to be OP_NEEDS_UNINSTALL so that the 
  1658.       // extension is uninstalled properly during the subsequent uninstall 
  1659.       // pass in |ExtensionManager::_finalizeOperations|
  1660.       LOG("upgradeExtensionChrome: failed for extension " + this._id + " - why " + 
  1661.           "not convert to the new chrome.manifest format while you're at it? " + 
  1662.           "Failure exception: " + e);
  1663.       showMessage("malformedRegistrationTitle", [], "malformedRegistrationMessage",
  1664.                   [BundleManager.appName]);
  1665.  
  1666.       var stageFile = this._installLocation.getStageFile(this._id);
  1667.       if (stageFile)
  1668.         this._installLocation.removeFile(stageFile);
  1669.  
  1670.       StartupCache.put(this._installLocation, this._id, OP_NEEDS_UNINSTALL, true);
  1671.       StartupCache.write();
  1672.     }
  1673.   }  
  1674. };
  1675.  
  1676. /**
  1677.  * Safely attempt to perform a caller-defined install operation for a given
  1678.  * item ID. Using aggressive success-safety checks, this function will attempt
  1679.  * to move an existing location for an item aside and then allow installation
  1680.  * into the appropriate folder. If any operation fails the installation will 
  1681.  * abort and roll back from the moved-aside old version.
  1682.  * @param   itemID
  1683.  *          The GUID of the item to perform the operation on.
  1684.  * @param   installLocation
  1685.  *          The Install Location where the item is installed.
  1686.  * @param   installCallback
  1687.  *          A caller supplied JS object with the following properties:
  1688.  *          "data"      A data parameter to be passed to the callback.
  1689.  *          "callback"  A function to perform the install operation. This
  1690.  *                      function is passed three parameters:
  1691.  *                      1. The GUID of the item being operated on.
  1692.  *                      2. The Install Location where the item is installed.
  1693.  *                      3. The "data" parameter on the installCallback object.
  1694.  */
  1695. function safeInstallOperation(itemID, installLocation, installCallback) {
  1696.   var movedFiles = [];
  1697.   
  1698.   /**
  1699.    * Reverts a deep move by moving backed up files back to their original
  1700.    * location.
  1701.    */
  1702.   function rollbackMove()
  1703.   {
  1704.     for (var i = 0; i < movedFiles.length; ++i) {
  1705.       var oldFile = movedFiles[i].oldFile;
  1706.       var newFile = movedFiles[i].newFile;
  1707.       try {
  1708.         newFile.moveTo(oldFile.parent, newFile.leafName);
  1709.       }
  1710.       catch (e) {
  1711.         LOG("safeInstallOperation: failed to roll back files after an install " + 
  1712.             "operation failed. Failed to roll back: " + newFile.path + " to: " + 
  1713.             oldFile.path + " ... aborting installation.");
  1714.         throw e;
  1715.       }
  1716.     }
  1717.   }
  1718.   
  1719.   /**
  1720.    * Moves a file to a new folder.
  1721.    * @param   file
  1722.    *          The file to move
  1723.    * @param   destination
  1724.    *          The target folder
  1725.    */
  1726.   function moveFile(file, destination) {
  1727.     try {
  1728.       var oldFile = file.clone();
  1729.       file.moveTo(destination, file.leafName);
  1730.       movedFiles.push({ oldFile: oldFile, newFile: file });
  1731.     }
  1732.     catch (e) {
  1733.       LOG("safeInstallOperation: failed to back up file: " + file.path + " to: " + 
  1734.           destination.path + " ... rolling back file moves and aborting " + 
  1735.           "installation.");
  1736.       rollbackMove();
  1737.       throw e;
  1738.     }
  1739.   }
  1740.   
  1741.   /**
  1742.    * Moves a directory to a new location. If any part of the move fails,
  1743.    * files already moved will be rolled back.
  1744.    * @param   sourceDir
  1745.    *          The directory to move
  1746.    * @param   targetDir
  1747.    *          The destination directory
  1748.    * @param   currentDir
  1749.    *          The current directory (a subdirectory of |sourceDir| or 
  1750.    *          |sourceDir| itself) we are moving files from.
  1751.    */
  1752.   function moveDirectory(sourceDir, targetDir, currentDir) {
  1753.     var entries = currentDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  1754.     while (true) {
  1755.       var entry = entries.nextFile;
  1756.       if (!entry)
  1757.         break;
  1758.       if (entry.isDirectory())
  1759.         moveDirectory(sourceDir, targetDir, entry);
  1760.       else if (entry instanceof nsILocalFile) {
  1761.         var rd = entry.getRelativeDescriptor(sourceDir);
  1762.         var destination = targetDir.clone().QueryInterface(nsILocalFile);
  1763.         destination.setRelativeDescriptor(targetDir, rd);
  1764.         moveFile(entry, destination.parent);
  1765.       }
  1766.     }
  1767.     entries.close();
  1768.   }
  1769.   
  1770.   /**
  1771.    * Removes the temporary backup directory where we stored files. 
  1772.    * @param   directory
  1773.    *          The backup directory to remove
  1774.    */
  1775.   function cleanUpTrash(directory) {
  1776.     try {
  1777.       // Us-generated. Safe.
  1778.       if (directory && directory.exists())
  1779.         removeDirRecursive(directory);
  1780.     }
  1781.     catch (e) {
  1782.       LOG("safeInstallOperation: failed to clean up the temporary backup of the " + 
  1783.           "older version: " + itemLocationTrash.path);
  1784.       // This is a non-fatal error. Annoying, but non-fatal. 
  1785.     }
  1786.   }
  1787.   
  1788.   var itemLocation = installLocation.getItemLocation(itemID);
  1789.   if (itemLocation.exists()) {
  1790.     var trashDirName = itemID + "-trash";
  1791.     var itemLocationTrash = itemLocation.parent.clone();
  1792.     itemLocationTrash.append(trashDirName);
  1793.     if (itemLocationTrash.exists()) {
  1794.       // We can remove recursively here since this is a folder we created, not
  1795.       // one the user specified. If this fails, it'll throw, and the caller 
  1796.       // should stop installation.
  1797.       try {
  1798.         removeDirRecursive(itemLocationTrash);
  1799.       }
  1800.       catch (e) {
  1801.         LOG("safeFileOperation: failed to remove existing trash directory " + 
  1802.             itemLocationTrash.path + " ... aborting installation.");
  1803.         throw e;
  1804.       }
  1805.     }
  1806.     
  1807.     // Move the directory that contains the existing version of the item aside, 
  1808.     // into {GUID}-trash. This will throw if there's a failure and the install
  1809.     // will abort.
  1810.     moveDirectory(itemLocation, itemLocationTrash, itemLocation);
  1811.     
  1812.     // Clean up the original location, if necessary. Again, this is a path we 
  1813.     // generated, so it is safe to recursively delete.
  1814.     try {
  1815.       removeDirRecursive(itemLocation);
  1816.     }
  1817.     catch (e) {
  1818.       LOG("safeInstallOperation: failed to clean up item location after its contents " + 
  1819.           "were properly backed up. Failed to clean up: " + itemLocation.path + 
  1820.           " ... rolling back file moves and aborting installation.");
  1821.       rollbackMove();
  1822.       cleanUpTrash(itemLocationTrash);
  1823.       throw e;
  1824.     }
  1825.   }
  1826.       
  1827.   // Now tell the client to do their stuff.
  1828.   try {
  1829.     installCallback.callback(itemID, installLocation, installCallback.data);
  1830.   }
  1831.   catch (e) {
  1832.     // This means the install operation failed. Remove everything and roll back.
  1833.     LOG("safeInstallOperation: install operation (caller-supplied callback) failed, " + 
  1834.         "rolling back file moves and aborting installation.");
  1835.     try {
  1836.       // Us-generated. Safe.
  1837.       removeDirRecursive(itemLocation);
  1838.     }
  1839.     catch (e) {
  1840.       LOG("safeInstallOperation: failed to remove the folder we failed to install " + 
  1841.           "an item into: " + itemLocation.path + " -- There is not much to suggest " + 
  1842.           "here... maybe restart and try again?");
  1843.       cleanUpTrash(itemLocationTrash);
  1844.       throw e;
  1845.     }
  1846.     rollbackMove();
  1847.     cleanUpTrash(itemLocationTrash);
  1848.     throw e;        
  1849.   }
  1850.   
  1851.   // Now, and only now - after everything else has succeeded (against all odds!) 
  1852.   // remove the {GUID}-trash directory where we stashed the old version of the 
  1853.   // item.
  1854.   cleanUpTrash(itemLocationTrash);
  1855. }
  1856.  
  1857. /**
  1858.  * Manages the list of pending operations.
  1859.  */
  1860. var PendingOperations = {
  1861.   _ops: { },
  1862.  
  1863.   /**
  1864.    * Adds an entry to the Pending Operations List
  1865.    * @param   opType
  1866.    *          The type of Operation to be performed
  1867.    * @param   entry
  1868.    *          A JS Object representing the item to be operated on:
  1869.    *          "locationKey"   The name of the Install Location where the item
  1870.    *                          is installed.
  1871.    *          "id"            The GUID of the item.
  1872.    */
  1873.   addItem: function(opType, entry) {
  1874.     if (!(opType in this._ops))
  1875.       this._ops[opType] = [];
  1876.     this._ops[opType].push(entry);
  1877.   },
  1878.   
  1879.   /**
  1880.    * Removes a Pending Operation from the list
  1881.    * @param   opType
  1882.    *          The type of Operation being removed
  1883.    * @param   id
  1884.    *          The GUID of the item to remove the entry for
  1885.    */
  1886.   clearItem: function(opType, id) {
  1887.     if (!(opType in this._ops))
  1888.       return;
  1889.     for (var i = 0; i < this._ops[opType].length; ++i) {
  1890.       if (this._ops[opType][i].id == id)
  1891.         this._ops[opType].splice(i, 1);
  1892.     }
  1893.   },
  1894.   
  1895.   /**
  1896.    * Remove all Pending Operations of a certain type
  1897.    * @param   opType
  1898.    *          The type of Operation to remove all entries for
  1899.    */
  1900.   clearItems: function(opType) {
  1901.     if (opType in this._ops)
  1902.       this._ops[opType] = [];
  1903.   },
  1904.   
  1905.   /**
  1906.    * Get an array of operations of a certain type
  1907.    * @param   opType
  1908.    *          The type of Operation to return a list of
  1909.    */
  1910.   getOperations: function(opType) {
  1911.     return opType in this._ops ? this._ops[opType] : [];
  1912.   },
  1913.   
  1914.   /**
  1915.    * The total number of Pending Operations, for all types.
  1916.    */
  1917.   get size() {
  1918.     var size = 0;
  1919.     for (var opType in this._ops)
  1920.       size += this._ops[opType].length;
  1921.     return size;
  1922.   }
  1923. };
  1924.  
  1925. /**
  1926.  * Manages registered Install Locations
  1927.  */
  1928. var InstallLocations = { 
  1929.   _locations: { },
  1930.  
  1931.   /**
  1932.    * A nsISimpleEnumerator of all available Install Locations.
  1933.    */
  1934.   get enumeration() {
  1935.     var installLocations = [];
  1936.     for (var key in this._locations) 
  1937.       installLocations.push(InstallLocations.get(key));
  1938.     return new ArrayEnumerator(installLocations);
  1939.   },
  1940.   
  1941.   /**
  1942.    * Gets a named Install Location
  1943.    * @param   name
  1944.    *          The name of the Install Location to get
  1945.    */
  1946.   get: function(name) {
  1947.     return name in this._locations ? this._locations[name] : null;
  1948.   },
  1949.   
  1950.   /**
  1951.    * Registers an Install Location
  1952.    * @param   installLocation
  1953.    *          The Install Location to register
  1954.    */
  1955.   put: function(installLocation) {
  1956.     this._locations[installLocation.name] = installLocation;
  1957.   }
  1958. };
  1959.  
  1960. /**
  1961.  * Manages the Startup Cache. The Startup Cache is a representation
  1962.  * of the contents of extensions.cache, a list of all
  1963.  * items the Extension System knows about, whether or not they
  1964.  * are active or visible.
  1965.  */
  1966. var StartupCache = {
  1967.   /**
  1968.    * Location Name -> GUID hash of entries from the Startup Cache file
  1969.    * Each entry has the following properties:
  1970.    *  "descriptor"    The location on disk of the item
  1971.    *  "mtime"         The time the location was last modified
  1972.    *  "op"            Any pending operations on this item.
  1973.    *  "location"      The Install Location name where the item is installed.
  1974.    */
  1975.   entries: { },
  1976.  
  1977.   /**
  1978.    * Puts an entry into the Startup Cache
  1979.    * @param   installLocation
  1980.    *          The Install Location where the item is installed
  1981.    * @param   id
  1982.    *          The GUID of the item
  1983.    * @param   op
  1984.    *          The name of the operation to be performed
  1985.    * @param   shouldCreate
  1986.    *          Whether or not we should create a new entry for this item
  1987.    *          in the cache if one does not already exist. 
  1988.    */
  1989.   put: function(installLocation, id, op, shouldCreate) {
  1990.     var itemLocation = installLocation.getItemLocation(id);
  1991.  
  1992.     var descriptor = null;
  1993.     var mtime = null;
  1994.     if (itemLocation) {
  1995.       itemLocation.QueryInterface(nsILocalFile);
  1996.       descriptor = getDescriptorFromFile(itemLocation, installLocation);
  1997.       if (itemLocation.exists() && itemLocation.isDirectory())
  1998.         mtime = Math.floor(itemLocation.lastModifiedTime / 1000);
  1999.     }
  2000.  
  2001.     this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate);
  2002.   },
  2003.  
  2004.   /**
  2005.    * Private helper function for putting an entry into the Startup Cache
  2006.    * without relying on the presence of its associated nsIInstallLocation
  2007.    * instance.
  2008.    *
  2009.    * @param key
  2010.    *        The install location name.
  2011.    * @param id
  2012.    *        The ID of the item.
  2013.    * @param descriptor
  2014.    *        Value returned from absoluteDescriptor.  May be null, in which
  2015.    *        case the descriptor field is not updated.
  2016.    * @param mtime
  2017.    *        The last modified time of the item.  May be null, in which case the
  2018.    *        descriptor field is not updated.
  2019.    * @param op
  2020.    *        The OP code to store with the entry.
  2021.    * @param shouldCreate
  2022.    *        Boolean value indicating whether to create or delete the entry.
  2023.    */
  2024.   _putRaw: function(key, id, descriptor, mtime, op, shouldCreate) {
  2025.     if (!(key in this.entries))
  2026.       this.entries[key] = { };
  2027.     if (!(id in this.entries[key]))
  2028.       this.entries[key][id] = { };
  2029.     if (shouldCreate) {
  2030.       if (!this.entries[key][id]) 
  2031.         this.entries[key][id] = { };
  2032.  
  2033.       var entry = this.entries[key][id];
  2034.  
  2035.       if (descriptor)
  2036.         entry.descriptor = descriptor;
  2037.       if (mtime) 
  2038.         entry.mtime = mtime;
  2039.       entry.op = op;
  2040.       entry.location = key;
  2041.     }
  2042.     else
  2043.       this.entries[key][id] = null;
  2044.   },
  2045.   
  2046.   /**
  2047.    * Clears an entry from the Startup Cache
  2048.    * @param   installLocation
  2049.    *          The Install Location where item is installed
  2050.    * @param   id
  2051.    *          The GUID of the item.
  2052.    */
  2053.   clearEntry: function(installLocation, id) {
  2054.     var key = installLocation.name;
  2055.     if (key in this.entries && id in this.entries[key])
  2056.       this.entries[key][id] = null;
  2057.   },
  2058.   
  2059.   /**
  2060.    * Get all the startup cache entries for a particular ID.
  2061.    * @param   id
  2062.    *          The GUID of the item to locate.
  2063.    * @returns An array of Startup Cache entries for the specified ID.
  2064.    */
  2065.   findEntries: function(id) {
  2066.     var entries = [];
  2067.     for (var key in this.entries) {
  2068.       if (id in this.entries[key]) 
  2069.         entries.push(this.entries[key][id]);
  2070.     }
  2071.     return entries;
  2072.   },
  2073.  
  2074.   /**
  2075.    * Call a function on each entry.  The callback function takes a single
  2076.    * parameter, which is an entry object.
  2077.    */
  2078.   forEachEntry: function(callback) {
  2079.     for (var key in this.entries) {
  2080.       for (id in this.entries[key])
  2081.         callback(this.entries[key][id]);
  2082.     }
  2083.   },
  2084.   
  2085.   /** 
  2086.    * Read the Item-Change manifest file into a hash of properties.
  2087.    * The Item-Change manifest currently holds a list of paths, with the last
  2088.    * mtime for each path, and the GUID of the item at that path.
  2089.    */
  2090.   read: function() {
  2091.     var itemChangeManifest = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2092.     if (!itemChangeManifest.exists()) {
  2093.       // There is no change manifest for some reason, either we're in an initial
  2094.       // state or something went wrong with one of the other files and the
  2095.       // change manifest was removed. Return an empty dataset and rebuild.
  2096.       return;
  2097.     }
  2098.     var fis = Components.classes["@mozilla.org/network/file-input-stream;1"]
  2099.                         .createInstance(Components.interfaces.nsIFileInputStream);
  2100.     fis.init(itemChangeManifest, -1, -1, false);
  2101.     if (fis instanceof nsILineInputStream) {
  2102.       var line = { value: "" };
  2103.       var more = false;
  2104.       do {
  2105.         more = fis.readLine(line);
  2106.         if (line.value) {
  2107.           // The Item-Change manifest is formatted like so:
  2108.           //  (pd = descriptor)
  2109.           // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2110.           // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op
  2111.           // ...
  2112.           // We hash on location-key first, because we don't want to have to 
  2113.           // spin up the main extensions datasource on every start to determine
  2114.           // the Install Location for an item.
  2115.           // We hash on guid second, because we want a way to quickly determine
  2116.           // item GUID during a check loop that runs on every startup.
  2117.           var parts = line.value.split("\t");
  2118.           var op = parts[4];
  2119.           this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true);
  2120.           if (op)
  2121.             PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] });
  2122.         }
  2123.       }
  2124.       while (more);
  2125.     }
  2126.     fis.close();
  2127.   },
  2128.  
  2129.   /**
  2130.    * Writes the Startup Cache to disk
  2131.    */
  2132.   write: function() {
  2133.     var extensionsCacheFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2134.     var fos = openSafeFileOutputStream(extensionsCacheFile);
  2135.     for (var locationKey in this.entries) {
  2136.       for (var id in this.entries[locationKey]) {
  2137.         var entry = this.entries[locationKey][id];
  2138.         if (entry) {
  2139.           try {
  2140.             var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey));
  2141.  
  2142.             // Update our knowledge of this item's last-modified-time.
  2143.             // XXXdarin: this may cause us to miss changes in some cases.
  2144.             var itemMTime = 0;
  2145.             if (itemLocation.exists() && itemLocation.isDirectory())
  2146.               itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000);
  2147.  
  2148.             // Each line in the startup cache manifest is in this form:
  2149.             // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
  2150.             var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" +
  2151.                        itemMTime + "\t" + entry.op + "\r\n";
  2152.             fos.write(line, line.length);
  2153.           }
  2154.           catch (e) {}
  2155.         }
  2156.       }
  2157.     }
  2158.     closeSafeFileOutputStream(fos);
  2159.   }
  2160. };
  2161.  
  2162. /**
  2163.  * Installs, manages and tracks compatibility for Extensions and Themes
  2164.  * @constructor
  2165.  */
  2166. function ExtensionManager() {
  2167.   gApp = Components.classes["@mozilla.org/xre/app-info;1"]
  2168.                    .getService(Components.interfaces.nsIXULAppInfo)
  2169.                    .QueryInterface(Components.interfaces.nsIXULRuntime);
  2170.   gOSTarget = gApp.OS;
  2171.   try {
  2172.     gXPCOMABI = gApp.XPCOMABI;
  2173.   } catch (ex) {
  2174.     // Provide a default for gXPCOMABI. It won't be compared to an
  2175.     // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
  2176.     // as targetPlatform), but it will be displayed in error messages and
  2177.     // transmitted to update URLs.
  2178.     gXPCOMABI = UNKNOWN_XPCOM_ABI;
  2179.   }
  2180.   gPref = Components.classes["@mozilla.org/preferences-service;1"]
  2181.                     .getService(Components.interfaces.nsIPrefBranch2);
  2182.   gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2183.   gPref.addObserver(PREF_EM_LOGGING_ENABLED, this, false);
  2184.  
  2185.   gOS = Components.classes["@mozilla.org/observer-service;1"]
  2186.                   .getService(Components.interfaces.nsIObserverService);
  2187.   gOS.addObserver(this, "xpcom-shutdown", false);
  2188.  
  2189.   gConsole = Components.classes["@mozilla.org/consoleservice;1"]
  2190.                        .getService(Components.interfaces.nsIConsoleService);  
  2191.   
  2192.   gRDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]
  2193.                    .getService(Components.interfaces.nsIRDFService);
  2194.   gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
  2195.   
  2196.   // Register Global Install Location
  2197.   var appGlobalExtensions = getDirNoCreate(KEY_APPDIR, [DIR_EXTENSIONS]);
  2198.   var priority = nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
  2199.   var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL, 
  2200.                                                     appGlobalExtensions, true,
  2201.                                                     priority);
  2202.   InstallLocations.put(globalLocation);
  2203.  
  2204.   // Register App-Profile Install Location
  2205.   var appProfileExtensions = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS]);
  2206.   var priority = nsIInstallLocation.PRIORITY_APP_PROFILE;
  2207.   var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE, 
  2208.                                                      appProfileExtensions, false,
  2209.                                                      priority);
  2210.   InstallLocations.put(profileLocation);
  2211.  
  2212. //@line 2298 "/build/buildd/mozilla-thunderbird-1.5.0.10/build-dir/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  2213.  
  2214.   // Register Additional Install Locations
  2215.   var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  2216.                                   .getService(Components.interfaces.nsICategoryManager);
  2217.   var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS);
  2218.   while (locations.hasMoreElements()) {
  2219.     var entry = locations.getNext().QueryInterface(Components.interfaces.nsISupportsCString).data;
  2220.     var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry);
  2221.     var location = Components.classes[contractID].getService(nsIInstallLocation);
  2222.     InstallLocations.put(location);
  2223.   }
  2224. }
  2225.  
  2226. ExtensionManager.prototype = {
  2227.   /**
  2228.    * See nsIObserver.idl
  2229.    */
  2230.   observe: function(subject, topic, data) {
  2231.     switch (topic) {
  2232.     case "app-startup":
  2233.       gOS.addObserver(this, "profile-after-change", false);
  2234.       break;
  2235.     case "profile-after-change":
  2236.       this._profileSelected();
  2237.       break;
  2238.     case "quit-application-requested":
  2239.       this._confirmCancelDownloadsOnQuit(subject);
  2240.       break;
  2241.     case "offline-requested":
  2242.       this._confirmCancelDownloadsOnOffline(subject);
  2243.       break;
  2244.     case "xpcom-shutdown":
  2245.       this._shutdown();
  2246.       break;
  2247.     case "nsPref:changed":
  2248.       if (data == PREF_EM_LOGGING_ENABLED)
  2249.         this._loggingToggled();
  2250.       break;
  2251.     }
  2252.   },
  2253.   
  2254.   /**
  2255.    * Refresh the logging enabled global from preferences when the user changes
  2256.    * the preference settting.
  2257.    */
  2258.   _loggingToggled: function() {
  2259.     gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
  2260.   },
  2261.  
  2262.   /**
  2263.    * Initialize the system after a profile has been selected.
  2264.    */  
  2265.   _profileSelected: function() {
  2266.     // Tell the Chrome Registry which Skin to select
  2267.     try {
  2268.       if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
  2269.         var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  2270.         gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
  2271.         gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
  2272.         gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
  2273.       }
  2274.     }
  2275.     catch (e) {
  2276.     }
  2277.   },
  2278.   
  2279.   /**
  2280.    * Clean up on application shutdown to avoid leaks.
  2281.    */
  2282.   _shutdown: function() {
  2283.     gOS.removeObserver(this, "xpcom-shutdown");    
  2284.  
  2285.     // Release strongly held services.
  2286.     gOS = null;
  2287.     if (this._ds && gRDF) 
  2288.       gRDF.UnregisterDataSource(this._ds)
  2289.     gRDF = null;
  2290.     if (gPref)
  2291.       gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
  2292.     gPref = null;
  2293.     gConsole = null;
  2294.     gVersionChecker = null;
  2295.     gInstallManifestRoot = null;
  2296.     gApp = null;
  2297.   },
  2298.   
  2299.   /**
  2300.    * Check for presence of critical Extension system files. If any is missing, 
  2301.    * delete the others and signal that the system needs to rebuild them all
  2302.    * from scratch.
  2303.    * @returns true if any critical file is missing and the system needs to
  2304.    *          be rebuilt, false otherwise.
  2305.    */
  2306.   _ensureDatasetIntegrity: function () {
  2307.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  2308.     var extensionsINI = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2309.     var extensionsCache = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_STARTUP_CACHE]);
  2310.     
  2311.     var dsExists = extensionsDS.exists();
  2312.     var iniExists = extensionsINI.exists();
  2313.     var cacheExists = extensionsCache.exists();
  2314.  
  2315.     if (dsExists && iniExists && cacheExists)
  2316.       return false;
  2317.  
  2318.     // If any of the files are missing, remove the .ini file
  2319.     if (iniExists)
  2320.       extensionsINI.remove(false);
  2321.  
  2322.     // If the extensions datasource is missing remove the .cache file if it exists
  2323.     if (!dsExists && cacheExists)
  2324.       extensionsCache.remove(false);
  2325.  
  2326.     return true;
  2327.   },
  2328.   
  2329.   /**
  2330.    * See nsIExtensionManager.idl
  2331.    */
  2332.   start: function(commandLine) {
  2333.     var isDirty = false;
  2334.     var forceAutoReg = false;
  2335.     
  2336.     // Somehow the component list went away, and for that reason the new one
  2337.     // generated by this function is going to result in a different compreg.
  2338.     // We must force a restart.
  2339.     var componentList = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  2340.     if (!componentList.exists())
  2341.       forceAutoReg = true;
  2342.     
  2343.     // Check for missing manifests - e.g. missing extensions.ini, missing
  2344.     // extensions.cache, extensions.rdf etc. If any of these files 
  2345.     // is missing then we are in some kind of weird or initial state and need
  2346.     // to force a regeneration.
  2347.     if (this._ensureDatasetIntegrity())
  2348.       isDirty = true;
  2349.  
  2350.     // Configure any items that are being installed, uninstalled or upgraded 
  2351.     // by being added, removed or modified by another process. We must do this
  2352.     // on every startup since there is no way we can tell if this has happened
  2353.     // or not!
  2354.     if (this._checkForFileChanges())
  2355.       isDirty = true;
  2356.  
  2357.     if (PendingOperations.size != 0)
  2358.       isDirty = true;
  2359.  
  2360.     // Extension Changes
  2361.     if (isDirty) {
  2362.       var needsRestart = this._finishOperations();
  2363.  
  2364.       if (forceAutoReg) {
  2365.         this._extensionListChanged = true;
  2366.         needsRestart = true;
  2367.       }
  2368.       return needsRestart;
  2369.     }
  2370.       
  2371.     this._startTimers();
  2372.  
  2373.     return false;
  2374.   },
  2375.   
  2376.   /**
  2377.    * Begins all background update check timers
  2378.    */
  2379.   _startTimers: function() {
  2380.     // Register a background update check timer
  2381.     var tm = 
  2382.         Components.classes["@mozilla.org/updates/timer-manager;1"]
  2383.                   .getService(Components.interfaces.nsIUpdateTimerManager);
  2384.     var interval = getPref("getIntPref", PREF_EM_UPDATE_INTERVAL, 86400); 
  2385.     tm.registerTimer("addon-background-update-timer", this, interval);
  2386.   },
  2387.   
  2388.   /**
  2389.    * Notified when a timer fires
  2390.    * @param   timer
  2391.    *          The timer that fired
  2392.    */
  2393.   notify: function(timer) {
  2394.     if (getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
  2395.       this.update([], 0, false, null);
  2396.   },
  2397.   
  2398.   /**
  2399.    * See nsIExtensionManager.idl
  2400.    */
  2401.   handleCommandLineArgs: function(commandLine) {
  2402.     try {
  2403.       var globalExtension = commandLine.handleFlagWithParam("install-global-extension", false);
  2404.       if (globalExtension) {
  2405.         var file = commandLine.resolveFile(globalExtension);
  2406.         this._installGlobalItem(file);
  2407.       }
  2408.       var globalTheme = commandLine.handleFlagWithParam("install-global-theme", false);
  2409.       if (globalTheme) {
  2410.         file = commandLine.resolveFile(globalTheme);
  2411.         this._installGlobalItem(file);
  2412.       }
  2413.     }
  2414.     catch (e) { 
  2415.       LOG("ExtensionManager:handleCommandLineArgs - failure, catching exception - lineno: " +
  2416.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  2417.     }
  2418.     commandLine.preventDefault = true;
  2419.   },
  2420.  
  2421.   /**
  2422.    * Installs an XPI/JAR file into the KEY_APP_GLOBAL install location.
  2423.    * @param   file
  2424.    *          The XPI/JAR file to extract
  2425.    */
  2426.   _installGlobalItem: function(file) {
  2427.     if (!file || !file.exists())
  2428.       throw new Error("Unable to find the file specified on the command line!");
  2429.     var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2430.     if (!installManifestFile.exists())
  2431.       throw new Error("The package is missing an install manifest!");
  2432.     var installManifest = getInstallManifest(installManifestFile);
  2433.     installManifestFile.remove(false);
  2434.     var installData = this._getInstallData(installManifest);
  2435.     var installer = new Installer(installManifest, installData.id,
  2436.                                   InstallLocations.get(KEY_APP_GLOBAL),
  2437.                                   installData.type);
  2438.     installer._installExtensionFiles(file);
  2439.     if (installData.type == nsIUpdateItem.TYPE_EXTENSION)
  2440.       installer.upgradeExtensionChrome();
  2441.     else
  2442.       installer.upgradeThemeChrome();
  2443.   },
  2444.  
  2445.   /**
  2446.    * Check to see if a file is a XPI/JAR file that the user dropped into this
  2447.    * Install Location. (i.e. a XPI that is not a staged XPI from an install 
  2448.    * transaction that is currently in operation). 
  2449.    * @param   file
  2450.    *          The XPI/JAR file to configure
  2451.    * @param   location
  2452.    *          The Install Location where this file was found.
  2453.    * @returns A nsIUpdateItem representing the dropped XPI if this file was a 
  2454.    *          XPI/JAR that needs installation, null otherwise.
  2455.    */
  2456.   _getItemForDroppedFile: function(file, location) {
  2457.     if (fileIsItemPackage(file)) {
  2458.       // We know nothing about this item, it is not something we've
  2459.       // staged in preparation for finalization, so assume it's something
  2460.       // the user dropped in.
  2461.       LOG("A Item Package appeared at: " + file.path + " that we know " + 
  2462.           "nothing about, assuming it was dropped in by the user and " + 
  2463.           "configuring for installation now. Location Key: " + location.name);
  2464.  
  2465.       var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
  2466.       if (!installManifestFile.exists())
  2467.         return null;
  2468.       var installManifest = getInstallManifest(installManifestFile);
  2469.       installManifestFile.remove(false);
  2470.       var ds = this.datasource;
  2471.       var installData = this._getInstallData(installManifest);
  2472.       var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest);
  2473.       return makeItem(installData.id,
  2474.                       installData.version,
  2475.                       location.name,
  2476.                       targetAppInfo ? targetAppInfo.minVersion : "",
  2477.                       targetAppInfo ? targetAppInfo.maxVersion : "",
  2478.                       getManifestProperty(installManifest, "name"),
  2479.                       "", /* XPI Update URL */
  2480.                       "", /* XPI Update Hash */
  2481.                       getManifestProperty(installManifest, "iconURL"),
  2482.                       getManifestProperty(installManifest, "updateURL"),
  2483.                       installData.type);
  2484.     }
  2485.     return null;
  2486.   },
  2487.   
  2488.   /**
  2489.    * Check for changes to items that were made independently of the Extension 
  2490.    * Manager, e.g. items were added or removed from a Install Location or items
  2491.    * in an Install Location changed. 
  2492.    */
  2493.   _checkForFileChanges: function() {
  2494.     var em = this;
  2495.     /** 
  2496.      * Configure an item that was installed or upgraded by another process
  2497.      * so that |_finishOperations| can properly complete processing and 
  2498.      * registration. 
  2499.      * As this is the only point at which we can reliably know the Install
  2500.      * Location of this item, we use this as an opportunity to:
  2501.      * 1. Check that this item is compatible with this Firefox version.
  2502.      * 2. If it is, configure the item by using the supplied callback.
  2503.      *    We do not do any special handling in the case that the item is
  2504.      *    not compatible with this version other than to simply not register
  2505.      *    it and log that fact - there is no "phone home" check for updates. 
  2506.      *    It may or may not make sense to do this, but for now we'll just
  2507.      *    not register.
  2508.      * @param   id
  2509.      *          The GUID of the item to validate and configure.
  2510.      * @param   location
  2511.      *          The Install Location where this item is installed.
  2512.      * @param   callback
  2513.      *          The callback that configures the item for installation upon
  2514.      *          successful validation.
  2515.      */      
  2516.     function installItem(id, location, callback) {
  2517.       // As this is the only pint at which we reliably know the installation
  2518.       var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  2519.       if (installRDF.exists()) {
  2520.         LOG("Item Installed/Upgraded at Install Location: " + location.name + 
  2521.             " Item ID: " + id + ", attempting to register...");
  2522.         var installManifest = getInstallManifest(installRDF);
  2523.         var installData = em._getInstallData(installManifest);
  2524.         if (installData.error == INSTALLERROR_SUCCESS) {
  2525.           LOG("... success, item is compatible");
  2526.           callback(installManifest, installData.id, location, installData.type);
  2527.         }
  2528.         else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) {
  2529.           LOG("... success, item installed but is not compatible");
  2530.           callback(installManifest, installData.id, location, installData.type);
  2531.           em._appDisableItem(id);
  2532.         }
  2533.         else {
  2534.           /**
  2535.            * Turns an error code into a message for logging
  2536.            * @param   error
  2537.            *          an Install Error code
  2538.            * @returns A string message to be logged.
  2539.            */
  2540.           function translateErrorMessage(error) {
  2541.             switch (error) {
  2542.             case INSTALLERROR_INVALID_GUID:
  2543.               return "Invalid GUID";
  2544.             case INSTALLERROR_INVALID_VERSION:
  2545.               return "Invalid Version";
  2546.             case INSTALLERROR_INCOMPATIBLE_VERSION:
  2547.               return "Incompatible Version";
  2548.             case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  2549.               return "Incompatible Platform";
  2550.             }
  2551.           }
  2552.           LOG("... failure, item is not compatible, error: " + 
  2553.               translateErrorMessage(installData.error));
  2554.  
  2555.           // Add the item to the Startup Cache anyway, so we don't re-detect it
  2556.           // every time the app starts.
  2557.           StartupCache.put(location, id, OP_NONE, true);
  2558.         }
  2559.       }      
  2560.     }
  2561.   
  2562.     /**
  2563.      * Determines if an item can be used based on whether or not the install
  2564.      * location of the "item" has an equal or higher priority than the install
  2565.      * location where another version may live.
  2566.      * @param   id
  2567.      *          The GUID of the item being installed.
  2568.      * @param   location
  2569.      *          The location where an item is to be installed.
  2570.      * @returns true if the item can be installed at that location, false 
  2571.      *          otherwise.
  2572.      */
  2573.     function canUse(id, location) {
  2574.       for (var locationKey in StartupCache.entries) {
  2575.         if (locationKey != location.name && 
  2576.             id in StartupCache.entries[locationKey]) {
  2577.           if (StartupCache.entries[locationKey][id]) {
  2578.             var oldInstallLocation = InstallLocations.get(locationKey);
  2579.             if (oldInstallLocation.priority <= location.priority)
  2580.               return false;
  2581.           }
  2582.         }
  2583.       }
  2584.       return true;
  2585.     }
  2586.     
  2587.     /** 
  2588.       * Gets a Dialog Param Block loaded with a set of strings to initialize the
  2589.       * XPInstall Confirmation Dialog.
  2590.       * @param   strings
  2591.       *          An array of strings
  2592.       * @returns A nsIDialogParamBlock loaded with the strings and dialog state.
  2593.       */
  2594.     function getParamBlock(strings) {
  2595.       var dpb = Components.classes["@mozilla.org/embedcomp/dialogparam;1"]
  2596.                           .createInstance(Components.interfaces.nsIDialogParamBlock);
  2597.       // OK and Cancel Buttons
  2598.       dpb.SetInt(0, 2);
  2599.       // Number of Strings
  2600.       dpb.SetInt(1, strings.length);
  2601.       dpb.SetNumberStrings(strings.length);
  2602.       // Add Strings
  2603.       for (var i = 0; i < strings.length; ++i)
  2604.         dpb.SetString(i, strings[i]);
  2605.       
  2606.       var supportsString = Components.classes["@mozilla.org/supports-string;1"]
  2607.                                      .createInstance(Components.interfaces.nsISupportsString);
  2608.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  2609.       supportsString.data = bundle.GetStringFromName("droppedInWarning");
  2610.       var objs = Components.classes["@mozilla.org/array;1"]
  2611.                            .createInstance(Components.interfaces.nsIMutableArray);
  2612.       objs.appendElement(supportsString, false);
  2613.       dpb.objects = objs;
  2614.       return dpb;        
  2615.     }
  2616.  
  2617.     /**
  2618.      * Installs a set of files which were dropped into an install location by 
  2619.      * the user, only after user confirmation.
  2620.      * @param   droppedInFiles
  2621.      *          An array of JS objects with the following properties:
  2622.      *          "file"      The nsILocalFile where the XPI lives
  2623.      *          "location"  The Install Location where the XPI was found. 
  2624.      * @param   xpinstallStrings
  2625.      *          An array of strings used to initialize the XPInstall Confirm 
  2626.      *          dialog.
  2627.      */ 
  2628.     function installDroppedInFiles(droppedInFiles, xpinstallStrings) {
  2629.       if (droppedInFiles.length == 0) 
  2630.         return;
  2631.         
  2632.       var dpb = getParamBlock(xpinstallStrings);
  2633.       var ifptr = Components.classes["@mozilla.org/supports-interface-pointer;1"]
  2634.                             .createInstance(Components.interfaces.nsISupportsInterfacePointer);
  2635.       ifptr.data = dpb;
  2636.       ifptr.dataIID = Components.interfaces.nsIDialogParamBlock;
  2637.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  2638.                           .getService(Components.interfaces.nsIWindowWatcher);
  2639.       ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG, 
  2640.                     "", "chrome,centerscreen,modal,dialog,titlebar", ifptr);
  2641.       if (!dpb.GetInt(0)) {
  2642.         // User said OK - install items
  2643.         for (var i = 0; i < droppedInFiles.length; ++i) {
  2644.           em.installItemFromFile(droppedInFiles[i].file, 
  2645.                                  droppedInFiles[i].location.name);
  2646.           // We are responsible for cleaning up this file
  2647.           droppedInFiles[i].file.remove(false);
  2648.         }
  2649.       }
  2650.       else {
  2651.         for (i = 0; i < droppedInFiles.length; ++i) {
  2652.           // We are responsible for cleaning up this file
  2653.           droppedInFiles[i].file.remove(false);
  2654.         }
  2655.       }
  2656.     }
  2657.     
  2658.     var isDirty = false;
  2659.     var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES,
  2660.                                      false);
  2661.     StartupCache.read();
  2662.     
  2663.     // Array of objects with 'location' and 'id' properties to maybe install.
  2664.     var newItems = [];
  2665.  
  2666.     var droppedInFiles = [];
  2667.     var xpinstallStrings = [];
  2668.     
  2669.     // Enumerate over the install locations from low to high priority.  The
  2670.     // enumeration returned is pre-sorted.
  2671.     var installLocations = this.installLocations;
  2672.     while (installLocations.hasMoreElements()) {
  2673.       var location = installLocations.getNext().QueryInterface(nsIInstallLocation);
  2674.  
  2675.       // Hash the set of items actually held by the Install Location.  
  2676.       var actualItems = { };
  2677.       var entries = location.itemLocations;
  2678.       while (true) {
  2679.         var entry = entries.nextFile;
  2680.         if (!entry)
  2681.           break;
  2682.  
  2683.         // Is this location a valid item? It must be a directory, and contain
  2684.         // an install.rdf manifest:
  2685.         if (entry.isDirectory()) {
  2686.           var installRDF = entry.clone();
  2687.           installRDF.append(FILE_INSTALL_MANIFEST);
  2688.  
  2689.           var id = location.getIDForLocation(entry);
  2690.           if (!id || (!installRDF.exists() && 
  2691.                       !location.itemIsManagedIndependently(id)))
  2692.             continue;
  2693.  
  2694.           actualItems[id] = entry;
  2695.         }
  2696.         else {
  2697.           // Check to see if this file is a XPI/JAR dropped into this dir
  2698.           // by the user, installing it if necessary. We do this here rather
  2699.           // than separately in |_finishOperations| because I don't want to
  2700.           // walk these lists multiple times on every startup.
  2701.           var item = this._getItemForDroppedFile(entry, location);
  2702.           if (item) {
  2703.             droppedInFiles.push({ file: entry, location: location });
  2704.  
  2705.             var zipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]
  2706.                                       .createInstance(Components.interfaces.nsIZipReader);
  2707.             zipReader.init(entry);
  2708.             var prettyName = "";
  2709.             try {
  2710.               var jar = zipReader.QueryInterface(Components.interfaces.nsIJAR);
  2711.               var principal = { };
  2712.               var certPrincipal = zipReader.getCertificatePrincipal(null, principal);
  2713.               // XXXbz This string could be empty.  This needs better
  2714.               // UI to present principal.value.certificate's subject.
  2715.               prettyName = principal.value.prettyName;
  2716.             }
  2717.             catch (e) { }
  2718.             xpinstallStrings = xpinstallStrings.concat([item.name, 
  2719.                                                         getURLSpecFromFile(entry),
  2720.                                                         item.iconURL, 
  2721.                                                         prettyName]);
  2722.             isDirty = true;
  2723.           }
  2724.         }
  2725.       }
  2726.       
  2727.       if (location.name in StartupCache.entries) {
  2728.         // Look for items that have been uninstalled by removing their directory.
  2729.         for (var id in StartupCache.entries[location.name]) {
  2730.           if (!StartupCache.entries[location.name] ||
  2731.               !StartupCache.entries[location.name][id]) 
  2732.             continue;
  2733.  
  2734.           // Force _finishOperations to run if we have enabled or disabled items.
  2735.           // XXXdarin this should be unnecessary now that we check
  2736.           // PendingOperations.size in start()
  2737.           if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE ||
  2738.               StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE)
  2739.             isDirty = true;
  2740.           
  2741.           if (!(id in actualItems) && 
  2742.               StartupCache.entries[location.name][id].op != OP_NEEDS_UNINSTALL &&
  2743.               StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL &&
  2744.               StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) {
  2745.             // We have an entry for this id in the Extensions database, for this 
  2746.             // install location, but it no longer exists in the Install Location. 
  2747.             // We can infer from this that the item has been removed, so uninstall
  2748.             // it properly. 
  2749.             if (canUse(id, location)) {
  2750.               LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor + 
  2751.                   " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item.");
  2752.               
  2753.               // Load the Extensions Datasource and force this item into the visible
  2754.               // items list if it is not already. This allows us to handle the case 
  2755.               // where there is an entry for an item in the Startup Cache but not
  2756.               // in the extensions.rdf file - in that case the item will not be in
  2757.               // the visible list and calls to |getInstallLocation| will mysteriously
  2758.               // fail.
  2759.               this.datasource.updateVisibleList(id, location.name, false);
  2760.               this.uninstallItem(id);
  2761.               isDirty = true;
  2762.             }
  2763.           }
  2764.           else if (!ignoreMTimeChanges) {
  2765.             // Look for items whose mtime has changed, and as such we can assume 
  2766.             // they have been "upgraded".
  2767.             var lf = { path: StartupCache.entries[location.name][id].descriptor };
  2768.             try {
  2769.                lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location);
  2770.             }
  2771.             catch (e) { }
  2772.  
  2773.             if (lf.exists && lf.exists()) {
  2774.               var actualMTime = Math.floor(lf.lastModifiedTime / 1000);
  2775.               if (actualMTime != StartupCache.entries[location.name][id].mtime) {
  2776.                 LOG("Item Location path changed: " + lf.path + " Item ID: " + 
  2777.                     id + " Location Key: " + location.name + ", attempting to upgrade item...");
  2778.                 if (canUse(id, location)) {
  2779.                   installItem(id, location, 
  2780.                               function(installManifest, id, location, type) {
  2781.                                 em._upgradeItem(installManifest, id, location, 
  2782.                                                 type);
  2783.                               });
  2784.                   isDirty = true;
  2785.                 }
  2786.               }
  2787.             }
  2788.             else {
  2789.               isDirty = true;
  2790.               LOG("Install Location returned a missing or malformed item path! " + 
  2791.                   "Item Path: " + lf.path + ", Location Key: " + location.name + 
  2792.                   " Item ID: " + id);
  2793.               if (canUse(id, location)) {
  2794.                 // Load the Extensions Datasource and force this item into the visible
  2795.                 // items list if it is not already. This allows us to handle the case 
  2796.                 // where there is an entry for an item in the Startup Cache but not
  2797.                 // in the extensions.rdf file - in that case the item will not be in
  2798.                 // the visible list and calls to |getInstallLocation| will mysteriously
  2799.                 // fail.
  2800.                 this.datasource.updateVisibleList(id, location.name, false);
  2801.                 this.uninstallItem(id);
  2802.               }
  2803.             }
  2804.           }
  2805.         }
  2806.       }
  2807.  
  2808.       // Look for items that have been installed by appearing in the location.
  2809.       for (var id in actualItems) {
  2810.         if (!(location.name in StartupCache.entries) || 
  2811.             !(id in StartupCache.entries[location.name]) ||
  2812.             !StartupCache.entries[location.name][id]) {
  2813.           // Remember that we've seen this item
  2814.           StartupCache.put(location, id, OP_NONE, true);
  2815.           // Push it on the stack of items to maybe install later
  2816.           newItems.push({location: location, id: id});
  2817.         }
  2818.       }
  2819.     }
  2820.  
  2821.     // Process any newly discovered items.  We do this here instead of in the
  2822.     // previous loop so that we can be sure that we have a fully populated
  2823.     // StartupCache.
  2824.     for (var i = 0; i < newItems.length; ++i) {
  2825.       var id = newItems[i].id;
  2826.       var location = newItems[i].location;
  2827.       if (canUse(id, location)) {
  2828.         LOG("Item Installed via directory addition to Install Location: " + 
  2829.             location.name + " Item ID: " + id + ", attempting to register...");
  2830.         installItem(id, location, 
  2831.                     function(installManifest, id, location, type) { 
  2832.                       em._configureForthcomingItem(installManifest, id, location, 
  2833.                                                    type);
  2834.                     });
  2835.         isDirty = true;
  2836.       }
  2837.     }
  2838.  
  2839.     // Ask the user if they want to install the dropped items, for security
  2840.     // purposes.
  2841.     installDroppedInFiles(droppedInFiles, xpinstallStrings);
  2842.     
  2843.     return isDirty;
  2844.   },
  2845.   
  2846.   /**
  2847.    * Upgrades contents.rdf files to chrome.manifest files for any existing
  2848.    * Extensions and Themes.
  2849.    * @returns true if actions were performed that require a restart, false 
  2850.    *          otherwise.
  2851.    */
  2852.   _upgradeChrome: function() {
  2853.     if (inSafeMode())
  2854.       return false;
  2855.  
  2856.     var checkForNewChrome = false;
  2857.     var ds = this.datasource;
  2858.     // If we have extensions that were installed before the new flat chrome
  2859.     // manifests, and are still valid, we need to manually create the flat
  2860.     // manifest files.
  2861.     var extensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION);
  2862.     for (var i = 0; i < extensions.length; ++i) {
  2863.       var e = extensions[i];
  2864.       var itemLocation = e.location.getItemLocation(e.id);
  2865.       var manifest = itemLocation.clone();
  2866.       manifest.append(FILE_CHROME_MANIFEST);
  2867.       if (!manifest.exists()) {
  2868.         var installRDF = itemLocation.clone();
  2869.         installRDF.append(FILE_INSTALL_MANIFEST);
  2870.         var installLocation = this.getInstallLocation(e.id);
  2871.         if (installLocation && installRDF.exists()) {
  2872.           var itemLocation = installLocation.getItemLocation(e.id);
  2873.           if (itemLocation.exists() && itemLocation.isDirectory()) {
  2874.             var installer = new Installer(ds, e.id, installLocation, 
  2875.                                           nsIUpdateItem.TYPE_EXTENSION);
  2876.             installer.upgradeExtensionChrome();
  2877.           }
  2878.         }
  2879.         else {
  2880.           ds.removeItemMetadata(e.id);
  2881.           ds.removeItemFromContainer(e.id);
  2882.         }
  2883.  
  2884.         checkForNewChrome = true;
  2885.       }
  2886.     }
  2887.  
  2888.     var themes = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  2889.     // If we have themes that were installed before the new flat chrome
  2890.     // manifests, and are still valid, we need to manually create the flat
  2891.     // manifest files.
  2892.     for (i = 0; i < themes.length; ++i) {
  2893.       var item = themes[i];
  2894.       var itemLocation = item.location.getItemLocation(item.id);
  2895.       var manifest = itemLocation.clone();
  2896.       manifest.append(FILE_CHROME_MANIFEST);
  2897.       if (manifest.exists() ||
  2898.           item.id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI))
  2899.         continue;
  2900.  
  2901.       var entries;
  2902.       try {
  2903.         var manifestURI = getURIFromFile(manifest);
  2904.         var chromeDir = itemLocation.clone();
  2905.         chromeDir.append(DIR_CHROME);
  2906.         
  2907.         if (!chromeDir.exists() || !chromeDir.isDirectory()) {
  2908.           ds.removeItemMetadata(item.id);
  2909.           ds.removeItemFromContainer(item.id);
  2910.           continue;
  2911.         }
  2912.  
  2913.         // We're relying on the fact that there is only one JAR file
  2914.         // in the "chrome" directory. This is a hack, but it works.
  2915.         entries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  2916.         var jarFile = entries.nextFile;
  2917.         if (jarFile) {
  2918.           var jarFileURI = getURIFromFile(jarFile);
  2919.           var contentsURI = newURI("jar:" + jarFileURI.spec + "!/");
  2920.  
  2921.           // Use the Chrome Registry API to install the theme there
  2922.           var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  2923.                             .getService(Components.interfaces.nsIToolkitChromeRegistry);
  2924.           cr.processContentsManifest(contentsURI, manifestURI, contentsURI, false, true);
  2925.         }
  2926.         entries.close();
  2927.       }
  2928.       catch (e) {
  2929.         LOG("_upgradeChrome: failed to upgrade contents manifest for " + 
  2930.             "theme: " + item.id + ", exception: " + e + "... The theme will be " + 
  2931.             "disabled.");
  2932.         this._appDisableItem(item.id);
  2933.       }
  2934.       finally {
  2935.         try {
  2936.           entries.close();
  2937.         }
  2938.         catch (e) {
  2939.         }
  2940.       }
  2941.       checkForNewChrome = true;
  2942.     }
  2943.     return checkForNewChrome;  
  2944.   },
  2945.   
  2946.   _checkForUncoveredItem: function(id) {
  2947.     var ds = this.datasource;
  2948.     var oldLocation = this.getInstallLocation(id);
  2949.     var newLocations = [];
  2950.     for (var locationKey in StartupCache.entries) {
  2951.       var location = InstallLocations.get(locationKey);
  2952.       if (id in StartupCache.entries[locationKey] && 
  2953.           location.priority > oldLocation.priority)
  2954.         newLocations.push(location);
  2955.     }
  2956.     newLocations.sort(function(a, b) { return b.priority - a.priority; });
  2957.     if (newLocations.length > 0) {
  2958.       for (var i = 0; i < newLocations.length; ++i) {
  2959.         // Check to see that the item at the location exists
  2960.         var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST);
  2961.         if (installRDF.exists()) {
  2962.           // Update the visible item cache so that |_finalizeUpgrade| is properly 
  2963.           // called from |_finishOperations|
  2964.           var name = newLocations[i].name;
  2965.           ds.updateVisibleList(id, name, true);
  2966.           PendingOperations.addItem(OP_NEEDS_UPGRADE, 
  2967.                                     { locationKey: name, id: id });
  2968.           PendingOperations.addItem(OP_NEEDS_INSTALL, 
  2969.                                     { locationKey: name, id: id });
  2970.           break;
  2971.         }
  2972.         else {
  2973.           // If no item exists at the location specified, remove this item
  2974.           // from the visible items list and check again. 
  2975.           StartupCache.clearEntry(newLocations[i], id);
  2976.           ds.updateVisibleList(id, null, true);
  2977.         }
  2978.       }
  2979.     }
  2980.     else
  2981.       ds.updateVisibleList(id, null, true);
  2982.   },
  2983.   
  2984.   /**
  2985.    * Finish up pending operations - perform upgrades, installs, enables/disables, 
  2986.    * uninstalls etc.
  2987.    * @returns true if actions were performed that require a restart, false 
  2988.    *          otherwise.
  2989.    */
  2990.   _finishOperations: function() {
  2991.     try {
  2992.       // Stuff has changed, load the Extensions datasource in all its RDFey
  2993.       // glory. 
  2994.       var ds = this.datasource;
  2995.       var updatedTargetAppInfos = [];
  2996.  
  2997.       var needsRestart = false;      
  2998.       do {
  2999.         // Enable and disable during startup so items that are changed in the
  3000.         // ui can be reset to a no-op.
  3001.         // Look for extensions that need to be enabled.
  3002.         var items = PendingOperations.getOperations(OP_NEEDS_ENABLE);
  3003.         for (var i = items.length - 1; i >= 0; --i) {
  3004.           var id = items[0].id;
  3005.           ds.setItemProperty(id, EM_R("userDisabled"), null);
  3006.           ds.setItemProperty(id, EM_R("appDisabled"), null);
  3007.           var installLocation = this.getInstallLocation(id);
  3008.           StartupCache.put(installLocation, id, OP_NONE, true);
  3009.           PendingOperations.clearItem(OP_NEEDS_ENABLE, id);
  3010.           needsRestart = true;
  3011.         }
  3012.         PendingOperations.clearItems(OP_NEEDS_ENABLE);
  3013.  
  3014.         // Look for extensions that need to be disabled.
  3015.         items = PendingOperations.getOperations(OP_NEEDS_DISABLE);
  3016.         for (i = items.length - 1; i >= 0; --i) {
  3017.           id = items[i].id;
  3018.           // Only set the userDisabled property when the item's appDisabled
  3019.           // property is not true since an item can't be disabled by the user
  3020.           // when it is already disabled by the application.
  3021.           if (ds.getItemProperty(id, "appDisabled") != "true")
  3022.             ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  3023.           installLocation = this.getInstallLocation(id);
  3024.           StartupCache.put(installLocation, id, OP_NONE, true);
  3025.           PendingOperations.clearItem(OP_NEEDS_DISABLE, id);
  3026.         }
  3027.         PendingOperations.clearItems(OP_NEEDS_DISABLE);
  3028.  
  3029.         // Look for extensions that need to be upgraded. The process here is to
  3030.         // uninstall the old version of the extension first, then install the
  3031.         // new version in its place. 
  3032.         items = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  3033.         for (i = items.length - 1; i >= 0; --i) {
  3034.           id = items[i].id;
  3035.           var oldLocation = this.getInstallLocation(id);
  3036.           var newLocation = InstallLocations.get(items[i].locationKey);
  3037.           if (newLocation.priority <= oldLocation.priority) {
  3038.             // check if there is updated app compatibility info
  3039.             var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3040.             if (newTargetAppInfo)
  3041.               updatedTargetAppInfos.push(newTargetAppInfo);
  3042.             this._finalizeUpgrade(id);
  3043.           }
  3044.         }
  3045.         PendingOperations.clearItems(OP_NEEDS_UPGRADE);
  3046.  
  3047.         // Install items
  3048.         items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3049.         for (i = items.length - 1; i >= 0; --i) {
  3050.           needsRestart = true;
  3051.           id = items[i].id;
  3052.           // check if there is updated app compatibility info
  3053.           newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
  3054.           if (newTargetAppInfo)
  3055.             updatedTargetAppInfos.push(newTargetAppInfo);
  3056.           this._finalizeInstall(id, null);
  3057.         }
  3058.         PendingOperations.clearItems(OP_NEEDS_INSTALL);
  3059.  
  3060.         // Look for extensions that need to be removed. This MUST be done after
  3061.         // the install operations since extensions to be installed may have to be
  3062.         // uninstalled if there are errors during the installation process!
  3063.         items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL);
  3064.         for (i = items.length - 1; i >= 0; --i) {
  3065.           id = items[i].id;
  3066.           this._finalizeUninstall(id);
  3067.           this._checkForUncoveredItem(id);
  3068.           needsRestart = true;
  3069.         }
  3070.         PendingOperations.clearItems(OP_NEEDS_UNINSTALL);
  3071.       }
  3072.       while (PendingOperations.size > 0);
  3073.       
  3074.       // Upgrade contents.rdf files to the new chrome.manifest format for
  3075.       // existing Extensions and Themes
  3076.       if (this._upgradeChrome()) {
  3077.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  3078.                            .getService(Components.interfaces.nsIChromeRegistry);
  3079.         cr.checkForNewChrome();
  3080.       }      
  3081.  
  3082.       // If no additional restart is required, it implies that there are
  3083.       // no new components that need registering so we can inform the app
  3084.       // not to do any extra startup checking next time round. 
  3085.       this._updateManifests(needsRestart);
  3086.  
  3087.       // If there is updated app compatibility info update the data sources.
  3088.       for (i = 0; i < updatedTargetAppInfos.length; ++i) {
  3089.         ds.updateTargetAppInfo(updatedTargetAppInfos[i].id,
  3090.                                updatedTargetAppInfos[i].minVersion,
  3091.                                updatedTargetAppInfos[i].maxVersion);
  3092.       }
  3093.     }
  3094.     catch (e) {
  3095.       LOG("ExtensionManager:_finishOperations - failure, catching exception - lineno: " +
  3096.           e.lineNumber + " - file: " + e.fileName + " - " + e);
  3097.     }
  3098.     return needsRestart;
  3099.   },
  3100.   
  3101.   /**
  3102.    * Checks to see if there are items that are incompatible with this version
  3103.    * of the application, disables them to prevent incompatibility problems and 
  3104.    * invokes the Update Wizard to look for newer versions.
  3105.    * @returns true if there were incompatible items installed and disabled, and
  3106.    *          the application must now be restarted to reinitialize XPCOM,
  3107.    *          false otherwise.
  3108.    */
  3109.   checkForMismatches: function() {
  3110.     // Check to see if the version of the application that is being started
  3111.     // now is the same one that was started last time. 
  3112.     var currAppVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  3113.                                  gApp.version);
  3114.     var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, "");
  3115.     if (currAppVersion == lastAppVersion)
  3116.       return false;
  3117.     // With a new profile lastAppVersion doesn't exist yet.
  3118.     if (!lastAppVersion) {
  3119.       gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3120.       return false;
  3121.     }
  3122.  
  3123.     // Version mismatch, we have to load the extensions datasource and do
  3124.     // version checking. Time hit here doesn't matter since this doesn't happen
  3125.     // all that often.
  3126.     var extensionsDS = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  3127.     if (!extensionsDS.exists())
  3128.       this._upgradeFromV10();
  3129.     
  3130.     // Make the extensions datasource consistent if it isn't already.
  3131.     var isDirty = false;
  3132.     if (this._ensureDatasetIntegrity())
  3133.       isDirty = true;
  3134.  
  3135.     if (this._checkForFileChanges())
  3136.       isDirty = true;
  3137.  
  3138.     if (PendingOperations.size != 0)
  3139.       isDirty = true;
  3140.  
  3141.     if (isDirty)
  3142.       this._finishOperations();
  3143.  
  3144.     var ds = this.datasource;
  3145.     var allAppManaged = true;
  3146.     var ctr = getContainer(ds, ds._itemRoot);
  3147.     var elements = ctr.GetElements();
  3148.     while (elements.hasMoreElements()) {
  3149.       var itemResource = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  3150.       var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
  3151.       if (ds.getItemProperty(id, "appManaged") == "true") {
  3152.         // Force an update of the metadata for appManaged extensions since the
  3153.         // last modified time is not updated for directories on FAT / FAT32
  3154.         // filesystems when software update applies a new version of the app.
  3155.         var location = this.getInstallLocation(id);
  3156.         if (location.name == KEY_APP_GLOBAL) {
  3157.           var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
  3158.           if (installRDF.exists()) {
  3159.             var metadataDS = getInstallManifest(installRDF);
  3160.             ds.addItemMetadata(id, metadataDS, location);
  3161.             ds.updateProperty(id, "compatible");
  3162.           }
  3163.         }
  3164.       }
  3165.       else if (allAppManaged)
  3166.         allAppManaged = false;
  3167.       if (ds.getItemProperty(id, "compatible") == "true") {
  3168.         if (ds.getItemProperty(id, "appDisabled"))
  3169.           ds.setItemProperty(id, EM_R("appDisabled"), null);
  3170.       }
  3171.       else if (!ds.getItemProperty(id, "appDisabled"))
  3172.         ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  3173.     }
  3174.     this._updateManifests(true);
  3175.  
  3176.     // Always check for compatibility updates when upgrading if we have add-ons
  3177.     // that aren't managed by the application.
  3178.     if (!allAppManaged)
  3179.       this._showMismatchWindow();
  3180.     
  3181.     // Finish any pending upgrades from the compatibility update to avoid an
  3182.     // additional restart.
  3183.     if (PendingOperations.size != 0)
  3184.       this._finishOperations();
  3185.  
  3186.     // Update the last app version so we don't do this again with this version.
  3187.     gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
  3188.  
  3189.     return true;
  3190.   },
  3191.  
  3192.   /**
  3193.    * Shows the "Compatibility Updates" UI
  3194.    */
  3195.   _showMismatchWindow: function(items) {
  3196.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3197.                        .getService(Components.interfaces.nsIWindowMediator);
  3198.     var wizard = wm.getMostRecentWindow("Update:Wizard");
  3199.     if (wizard)
  3200.       wizard.focus();
  3201.     else {
  3202.       var features = "chrome,centerscreen,dialog,titlebar,modal";
  3203.       // This *must* be modal so as not to break startup! This code is invoked before
  3204.       // the main event loop is initiated (via checkForMismatches).
  3205.       var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3206.                          .getService(Components.interfaces.nsIWindowWatcher);
  3207.       ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, null);
  3208.     }
  3209.   },
  3210.   
  3211.   /*
  3212.    * Catch all for facilitating a version 1.0 profile upgrade.
  3213.    * 1) removes the abandoned default theme directory from the profile.
  3214.    * 2) prepares themes installed with version 1.0 for installation.
  3215.    * 3) initiates an install to populate the new extensions datasource.
  3216.    * 4) migrates the disabled attribute from the old datasource.
  3217.    * 5) migrates the app compatibility info from the old datasource.
  3218.    */
  3219.   _upgradeFromV10: function() {
  3220.     // return early if the version 1.0 extensions datasource file doesn't exist.
  3221.     var oldExtensionsFile = getFile(KEY_PROFILEDIR, [DIR_EXTENSIONS, "Extensions.rdf"]);
  3222.     if (!oldExtensionsFile.exists())
  3223.       return;
  3224.  
  3225.     // Version 1.0 profiles have a default theme directory in the profile's
  3226.     // extensions directory that will be disabled due to having a maxVersion
  3227.     // of 1.0 so we must remove it if it exists.
  3228.     var profileDefaultTheme = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3229.                                              stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)]);
  3230.     if (profileDefaultTheme && profileDefaultTheme.exists())
  3231.       removeDirRecursive(profileDefaultTheme);
  3232.  
  3233.     // Version 0.9 profiles may have DOMi 1.0 with just an install.rdf
  3234.     var profileDOMi = getDirNoCreate(KEY_PROFILEDS, [DIR_EXTENSIONS,
  3235.                                      "{641d8d09-7dda-4850-8228-ac0ab65e2ac9}"]);
  3236.     if (profileDOMi && profileDOMi.exists())
  3237.       removeDirRecursive(profileDOMi);
  3238.  
  3239.     // Prepare themes for installation
  3240.     // Only enumerate directories in the app-profile and app-global locations.
  3241.     var locations = [KEY_APP_PROFILE, KEY_APP_GLOBAL];
  3242.     for (var i = 0; i < locations.length; ++i) {
  3243.       var location = InstallLocations.get(locations[i]);
  3244.       if (!location.canAccess)
  3245.         continue;
  3246.  
  3247.       var entries = location.itemLocations;
  3248.       var entry;
  3249.       while ((entry = entries.nextFile)) {
  3250.         var installRDF = entry.clone();
  3251.         installRDF.append(FILE_INSTALL_MANIFEST);
  3252.  
  3253.         var chromeDir = entry.clone();
  3254.         chromeDir.append(DIR_CHROME);
  3255.  
  3256.         // It must be a directory without an install.rdf and it must contain
  3257.         // a chrome directory
  3258.         if (!entry.isDirectory() || installRDF.exists() || !chromeDir.exists())
  3259.           continue;
  3260.  
  3261.         var chromeEntries = chromeDir.directoryEntries.QueryInterface(nsIDirectoryEnumerator);
  3262.         if (!chromeEntries.hasMoreElements())
  3263.           continue;
  3264.  
  3265.         // We're relying on the fact that there is only one JAR file
  3266.         // in the "chrome" directory. This is a hack, but it works.
  3267.         var jarFile = chromeEntries.nextFile;
  3268.         var id = location.getIDForLocation(entry);
  3269.  
  3270.         try {
  3271.           var zipReader = getZipReaderForFile(jarFile);
  3272.           zipReader.extract(FILE_INSTALL_MANIFEST, installRDF);
  3273.  
  3274.           var contentsManifestFile = location.getItemFile(id, FILE_CONTENTS_MANIFEST);
  3275.           zipReader.extract(FILE_CONTENTS_MANIFEST, contentsManifestFile);
  3276.  
  3277.           var rootFiles = ["preview.png", "icon.png"];
  3278.           for (var i = 0; i < rootFiles.length; ++i) {
  3279.             try {
  3280.               var target = location.getItemFile(id, rootFiles[i]);
  3281.               zipReader.extract(rootFiles[i], target);
  3282.             }
  3283.             catch (e) {
  3284.             }
  3285.           }
  3286.         }
  3287.         catch (e) {
  3288.           LOG("ExtensionManager:_upgradeFromV10 - failed to extract theme files\r\n" +
  3289.               "Exception: " + e);
  3290.         }
  3291.         zipReader.close();
  3292.       }
  3293.     }
  3294.  
  3295.     // When upgrading from a version 1.0 profile we need to populate the
  3296.     // extensions datasource with all items before checking for incompatible
  3297.     // items since the datasource hasn't been created yet.
  3298.     var itemsToCheck = [];
  3299.     if (this._checkForFileChanges()) {
  3300.       // Create a list of all items that are to be installed so we can migrate
  3301.       // these items's settings to the new datasource.
  3302.       var items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
  3303.       for (i = items.length - 1; i >= 0; --i) {
  3304.         if (items[i].locationKey == KEY_APP_PROFILE ||
  3305.             items[i].locationKey == KEY_APP_GLOBAL)
  3306.           itemsToCheck.push(items[i].id);
  3307.       }
  3308.       this._finishOperations();
  3309.     }
  3310.  
  3311.     // If there are no items to migrate settings for return early.
  3312.     if (itemsToCheck.length == 0)
  3313.       return;
  3314.  
  3315.     var fileURL = getURLSpecFromFile(oldExtensionsFile);
  3316.     var oldExtensionsDS = gRDF.GetDataSourceBlocking(fileURL);
  3317.     var versionChecker = getVersionChecker();
  3318.     var ds = this.datasource;
  3319.     var currAppVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  3320.                                  gApp.version);
  3321.     var currAppID = gApp.ID;
  3322.     for (var i = 0; i < itemsToCheck.length; ++i) {
  3323.       var item = ds.getItemForID(itemsToCheck[i]);
  3324.       var oldPrefix = (item.type == nsIUpdateItem.TYPE_EXTENSION) ? PREFIX_EXTENSION : PREFIX_THEME;
  3325.       var oldRes = gRDF.GetResource(oldPrefix + item.id);
  3326.       // Disable the item if it was disabled in the version 1.0 extensions
  3327.       // datasource.
  3328.       if (oldExtensionsDS.GetTarget(oldRes, EM_R("disabled"), true))
  3329.         ds.setItemProperty(item.id, EM_R("userDisabled"), EM_L("true"));
  3330.  
  3331.       // app enable all items. If it is incompatible it will be app disabled
  3332.       // later on.
  3333.       ds.setItemProperty(item.id, EM_R("appDisabled"), null);
  3334.  
  3335.       // if the item is already compatible don't attempt to migrate the
  3336.       // item's compatibility info
  3337.       var newRes = getResourceForID(itemsToCheck[i]);
  3338.       if (ds.isCompatible(ds, newRes))
  3339.         continue;
  3340.  
  3341.       var updatedMinVersion = null;
  3342.       var updatedMaxVersion = null;
  3343.       var targetApps = oldExtensionsDS.GetTargets(oldRes, EM_R("targetApplication"), true);
  3344.       while (targetApps.hasMoreElements()) {
  3345.         var targetApp = targetApps.getNext();
  3346.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  3347.           try {
  3348.             var foundAppID = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("id"), true));
  3349.             if (foundAppID != currAppID) // Different target application
  3350.               continue;
  3351.  
  3352.             updatedMinVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("minVersion"), true));
  3353.             updatedMaxVersion = stringData(oldExtensionsDS.GetTarget(targetApp, EM_R("maxVersion"), true));
  3354.  
  3355.             // Only set the target app info if the extension's target app info
  3356.             // in the version 1.0 extensions datasource makes it compatible
  3357.             if (versionChecker.compare(currAppVersion, updatedMinVersion) >= 0 &&
  3358.                 versionChecker.compare(currAppVersion, updatedMaxVersion) <= 0)
  3359.               ds.updateTargetAppInfo(item.id, updatedMinVersion, updatedMaxVersion);
  3360.  
  3361.             break;
  3362.           }
  3363.           catch (e) { 
  3364.           }
  3365.         }
  3366.       }
  3367.     }
  3368.   },
  3369.  
  3370.   /**
  3371.    * Write the Extensions List and the Startup Cache
  3372.    * @param   needsRestart
  3373.    *          true if the application needs to restart again, false otherwise.
  3374.    */  
  3375.   _updateManifests: function(needsRestart) {
  3376.     // Write the Startup Cache (All Items, visible or not)
  3377.     StartupCache.write();
  3378.     // Write the Extensions Locations Manifest (Visible, enabled items)
  3379.     this._updateExtensionsManifest(needsRestart);
  3380.   },
  3381.  
  3382.   /**
  3383.    * Get a list of items that are currently "active" (turned on) of a specific
  3384.    * type
  3385.    * @param   type
  3386.    *          The nsIUpdateItem type to return a list of items of
  3387.    * @returns An array of active items of the specified type.
  3388.    */
  3389.   _getActiveItems: function(type) {
  3390.     var allItems = this.getItemList(type, { });
  3391.     var activeItems = [];
  3392.     var ds = this.datasource;
  3393.     for (var i = 0; i < allItems.length; ++i) {
  3394.       var item = allItems[i];
  3395.  
  3396.       // An item entry is valid only if it is not disabled, not about to 
  3397.       // be disabled, and not about to be uninstalled.
  3398.       var installLocation = this.getInstallLocation(item.id);
  3399.       if (installLocation.name in StartupCache.entries &&
  3400.           item.id in StartupCache.entries[installLocation.name] &&
  3401.           StartupCache.entries[installLocation.name][item.id]) {
  3402.         var op = StartupCache.entries[installLocation.name][item.id].op;
  3403.         if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE || 
  3404.             op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE)
  3405.           continue;
  3406.       }
  3407.       // Suppress items that have been disabled by the user or the app.
  3408.       if (ds.getItemProperty(item.id, "disabled") != "true")
  3409.         activeItems.push({ id: item.id, location: installLocation });
  3410.     }
  3411.  
  3412.     return activeItems;
  3413.   },
  3414.   
  3415.   /**
  3416.    * Write the Extensions List
  3417.    * @param   needsRestart
  3418.    *          true if the application needs to restart again, false otherwise.
  3419.    */
  3420.   _updateExtensionsManifest: function(needsRestart) {
  3421.     // When an operation is performed that requires a component re-registration
  3422.     // (extension enabled/disabled, installed, uninstalled), we must write the
  3423.     // set of paths where extensions live so that the startup system can determine
  3424.     // where additional components, preferences, chrome manifests etc live.
  3425.     //
  3426.     // To do this we obtain a list of active extensions and themes and write 
  3427.     // these to the extensions.ini file in the profile directory.
  3428.     var validExtensions = this._getActiveItems(nsIUpdateItem.TYPE_EXTENSION);
  3429.     var validThemes     = this._getActiveItems(nsIUpdateItem.TYPE_THEME);
  3430.  
  3431.     var extensionsLocationsFile = getFile(KEY_PROFILEDIR, [FILE_EXTENSION_MANIFEST]);
  3432.     var fos = openSafeFileOutputStream(extensionsLocationsFile);
  3433.         
  3434.     var extensionSectionHeader = "[ExtensionDirs]\r\n";
  3435.     fos.write(extensionSectionHeader, extensionSectionHeader.length);
  3436.     for (i = 0; i < validExtensions.length; ++i) {
  3437.       var e = validExtensions[i];
  3438.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  3439.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3440.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3441.       fos.write(line, line.length);
  3442.     }
  3443.  
  3444.     var themeSectionHeader = "[ThemeDirs]\r\n";
  3445.     fos.write(themeSectionHeader, themeSectionHeader.length);
  3446.     for (i = 0; i < validThemes.length; ++i) {
  3447.       var e = validThemes[i];
  3448.       var itemLocation = e.location.getItemLocation(e.id).QueryInterface(nsILocalFile);
  3449.       var descriptor = getAbsoluteDescriptor(itemLocation);
  3450.       var line = "Extension" + i + "=" + descriptor + "\r\n";
  3451.       fos.write(line, line.length);
  3452.     }
  3453.  
  3454.     closeSafeFileOutputStream(fos);
  3455.  
  3456.     // Now refresh the compatibility manifest.
  3457.     this._extensionListChanged = needsRestart;
  3458.   },
  3459.   
  3460.   /**
  3461.    * Say whether or not the Extension List has changed (and thus whether or not
  3462.    * the system will have to restart the next time it is started).
  3463.    * @param   val
  3464.    *          true if the Extension List has changed, false otherwise.
  3465.    * @returns |val|
  3466.    */
  3467.   set _extensionListChanged(val) {
  3468.     // When an extension has an operation perform on it (e.g. install, upgrade,
  3469.     // disable, etc.) we are responsible for creating the .autoreg file and
  3470.     // nsAppRunner is responsible for removing it on restart. At some point it
  3471.     // may make sense to be able to cancel a registration but for now we only
  3472.     // create the file.
  3473.     try {
  3474.       var autoregFile = getFile(KEY_PROFILEDIR, [FILE_AUTOREG]);
  3475.       if (val && !autoregFile.exists())
  3476.         autoregFile.create(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  3477.     }
  3478.     catch (e) {
  3479.     }
  3480.     return val;
  3481.   },
  3482.   
  3483.   /**
  3484.    * Gathers data about an item specified by the supplied Install Manifest
  3485.    * and determines whether or not it can be installed as-is. It makes this 
  3486.    * determination by validating the item's GUID, Version, and determining 
  3487.    * if it is compatible with this application.
  3488.    * @param   installManifest 
  3489.    *          A nsIRDFDataSource representing the Install Manifest of the 
  3490.    *          item to be installed.
  3491.    * @return  A JS Object with the following properties:
  3492.    *          "id"       The GUID of the Item being installed.
  3493.    *          "version"  The Version string of the Item being installed.
  3494.    *          "name"     The Name of the Item being installed.
  3495.    *          "type"     The nsIUpdateItem type of the Item being installed.
  3496.    *          "targetApps" An array of TargetApplication Info Objects
  3497.    *                     with "id", "minVersion" and "maxVersion" properties,
  3498.    *                     representing applications targeted by this item.
  3499.    *          "error"    The result code:
  3500.    *                     INSTALLERROR_SUCCESS      
  3501.    *                       no error, item can be installed
  3502.    *                     INSTALLERROR_INVALID_GUID 
  3503.    *                       error, GUID is not well-formed
  3504.    *                     INSTALLERROR_INVALID_VERSION
  3505.    *                       error, Version is not well-formed
  3506.    *                     INSTALLERROR_INCOMPATIBLE_VERSION
  3507.    *                       error, item is not compatible with this version
  3508.    *                       of the application.
  3509.    *                     INSTALLERROR_INCOMPATIBLE_PLATFORM
  3510.    *                       error, item is not compatible with the operating
  3511.    *                       system or ABI the application was built for.
  3512.    */
  3513.   _getInstallData: function(installManifest) {
  3514.     var installData = { id          : "", 
  3515.                         version     : "", 
  3516.                         name        : "", 
  3517.                         type        : 0, 
  3518.                         error       : INSTALLERROR_SUCCESS, 
  3519.                         targetApps  : [],
  3520.                         currentApp  : null };
  3521.  
  3522.     // Fetch properties from the Install Manifest
  3523.     installData.id       = getManifestProperty(installManifest, "id");
  3524.     installData.version  = getManifestProperty(installManifest, "version");
  3525.     installData.name     = getManifestProperty(installManifest, "name");
  3526.     installData.type     = getAddonTypeFromInstallManifest(installManifest);
  3527.     installData.updateURL= getManifestProperty(installManifest, "updateURL");
  3528.  
  3529.     /**
  3530.      * Reads a property off a Target Application resource
  3531.      * @param   resource
  3532.      *          The RDF Resource for a Target Application
  3533.      * @param   property
  3534.      *          The property (less EM_NS) to read
  3535.      * @returns The string literal value of the property.
  3536.      */
  3537.     function readTAProperty(resource, property) {
  3538.       return stringData(installManifest.GetTarget(resource, EM_R(property), true));
  3539.     }
  3540.     
  3541.     var targetApps = installManifest.GetTargets(gInstallManifestRoot, 
  3542.                                                 EM_R("targetApplication"), 
  3543.                                                 true);
  3544.     while (targetApps.hasMoreElements()) {
  3545.       var targetApp = targetApps.getNext();
  3546.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  3547.         try {
  3548.           var data = { id        : readTAProperty(targetApp, "id"),
  3549.                        minVersion: readTAProperty(targetApp, "minVersion"),
  3550.                        maxVersion: readTAProperty(targetApp, "maxVersion") };
  3551.           installData.targetApps.push(data);
  3552.           if (data.id == gApp.ID) 
  3553.             installData.currentApp = data;
  3554.         }
  3555.         catch (e) {
  3556.           continue;
  3557.         }
  3558.       }
  3559.     }
  3560.  
  3561.     // If the item specifies one or more target platforms, make sure our OS/ABI
  3562.     // combination is in the list - otherwise, refuse to install the item.
  3563.     var targetPlatforms = null;
  3564.     try {
  3565.       targetPlatforms = installManifest.GetTargets(gInstallManifestRoot, 
  3566.                                                    EM_R("targetPlatform"), 
  3567.                                                    true);
  3568.     } catch(e) {
  3569.       // No targetPlatform nodes, continue.
  3570.     }
  3571.     if (targetPlatforms != null && targetPlatforms.hasMoreElements()) {
  3572.       var foundMatchingOS = false;
  3573.       var foundMatchingOSAndABI = false;
  3574.       var requireABICompatibility = false;
  3575.       while (targetPlatforms.hasMoreElements()) {
  3576.         var targetPlatform = stringData(targetPlatforms.getNext());
  3577.         var tokens = targetPlatform.split("_");
  3578.         var os = tokens[0];
  3579.         var abi = (tokens.length > 1) ? tokens[1] : null;
  3580.         if (os == gOSTarget) {
  3581.           foundMatchingOS = true;
  3582.           // The presence of any ABI part after our OS means ABI is important.
  3583.           if (abi != null) {
  3584.             requireABICompatibility = true;
  3585.             // If we don't know our ABI, we can't be compatible
  3586.             if (abi == gXPCOMABI && abi != UNKNOWN_XPCOM_ABI) {
  3587.               foundMatchingOSAndABI = true;
  3588.               break;
  3589.             }
  3590.           }
  3591.         }
  3592.       }
  3593.       if (!foundMatchingOS || (requireABICompatibility && !foundMatchingOSAndABI)) {
  3594.         installData.error = INSTALLERROR_INCOMPATIBLE_PLATFORM;
  3595.         return installData;
  3596.       }
  3597.     }
  3598.  
  3599.     // Validate the Item ID
  3600.     if (!gIDTest.test(installData.id)) {
  3601.       installData.error = INSTALLERROR_INVALID_GUID;
  3602.       return installData;
  3603.     }
  3604.      
  3605.     // Check the target application range specified by the extension metadata.
  3606.     if (!this.datasource.isCompatible(installManifest, gInstallManifestRoot, undefined))
  3607.       installData.error = INSTALLERROR_INCOMPATIBLE_VERSION;
  3608.     
  3609.     return installData;
  3610.   },  
  3611.   
  3612.   /**
  3613.    * Installs an item from a XPI/JAR file. 
  3614.    * This is the main entry point into the Install system from outside code
  3615.    * (e.g. XPInstall).
  3616.    * @param   aXPIFile
  3617.    *          The file to install from.
  3618.    * @param   aInstallLocationKey
  3619.    *          The name of the Install Location where this item should be 
  3620.    *          installed.
  3621.    */  
  3622.   installItemFromFile: function(xpiFile, installLocationKey) {
  3623.     this.installItemFromFileInternal(xpiFile, installLocationKey, null);
  3624.   },
  3625.   
  3626.   /**
  3627.    * Installs an item from a XPI/JAR file.
  3628.    * @param   aXPIFile
  3629.    *          The file to install from.
  3630.    * @param   aInstallLocationKey
  3631.    *          The name of the Install Location where this item should be 
  3632.    *          installed.
  3633.    * @param   aInstallManifest
  3634.    *          An updated Install Manifest from the Version Update check.
  3635.    *          Can be null when invoked from callers other than the Version
  3636.    *          Update check.
  3637.    */
  3638.   installItemFromFileInternal: function(aXPIFile, aInstallLocationKey, aInstallManifest) {
  3639.     var em = this;
  3640.     /**
  3641.      * Gets the Install Location for an Item.
  3642.      * @param   itemID 
  3643.      *          The GUID of the item to find an Install Location for.
  3644.      * @return  An object implementing nsIInstallLocation which represents the 
  3645.      *          location where the specified item should be installed. 
  3646.      *          This can be:
  3647.      *          1. an object that corresponds to the location key supplied to
  3648.      *             |installItemFromFileInternal|,
  3649.      *          2. the default install location (the App Profile Extensions Folder)
  3650.      *             if no location key was supplied, or the location key supplied
  3651.      *             was not in the set of registered locations
  3652.      *          3. null, if the location selected by 1 or 2 above does not support
  3653.      *             installs from XPI/JAR files, or that location is not writable 
  3654.      *             with the current access privileges.
  3655.      */
  3656.     function getInstallLocation(itemID) {
  3657.       // Here I use "upgrade" to mean "install a different version of an item".
  3658.       var installLocation = em.getInstallLocation(itemID);
  3659.       if (!installLocation) {
  3660.         // This is not an "upgrade", since we don't have any location data for the
  3661.         // extension ID specified - that is, it's not in our database.
  3662.  
  3663.         // Caller supplied a key to a registered location, use that location
  3664.         // for the installation
  3665.         installLocation = InstallLocations.get(aInstallLocationKey);
  3666.         if (installLocation) {
  3667.           // If the specified location does not have a common metadata location
  3668.           // (e.g. extensions have no common root, or other location specified
  3669.           // by the location implementation) - e.g. for a Registry Key enumeration
  3670.           // location - we cannot install or upgrade using a XPI file, probably
  3671.           // because these application types will be handling upgrading themselves.
  3672.           // Just bail.
  3673.           if (!installLocation.location) {
  3674.             LOG("Install Location \"" + installLocation.name + "\" does not support " + 
  3675.                 "installation of items from XPI/JAR files. You must manage " + 
  3676.                 "installation and update of these items yourself.");
  3677.             installLocation = null;
  3678.           }
  3679.         }
  3680.         else {
  3681.           // In the absence of a preferred install location, just default to
  3682.           // the App-Profile 
  3683.           installLocation = InstallLocations.get(KEY_APP_PROFILE);
  3684.         }
  3685.       } 
  3686.       else {
  3687.         // This is an "upgrade", but not through the Update System, because the
  3688.         // Update code will not let an extension with an incompatible target
  3689.         // app version range through to this point. This is an "upgrade" in the
  3690.         // sense that the user found a different version of an installed extension
  3691.         // and installed it through the web interface, so we have metadata.
  3692.         
  3693.         // If the location is different, return the preferred location rather than
  3694.         // the location of the currently installed version, because we may be in
  3695.         // the situation where an item is being installed into the global app 
  3696.         // dir when there's a version in the profile dir.
  3697.         if (installLocation.name != aInstallLocationKey) 
  3698.           installLocation = InstallLocations.get(aInstallLocationKey);
  3699.       }
  3700.       if (!installLocation.canAccess) {
  3701.         LOG("Install Location\"" + installLocation.name + "\" cannot be written " +
  3702.             "to with your access privileges. Installation will not proceed.");
  3703.         installLocation = null;
  3704.       }
  3705.       return installLocation;
  3706.     }
  3707.     
  3708.     /**
  3709.      * Stages a XPI file in the default item location specified by other 
  3710.      * applications when they registered with XulRunner if the item's
  3711.      * install manifest specified compatibility with them.
  3712.      */
  3713.     function stageXPIForOtherApps(xpiFile, installData) {
  3714.       for (var i = 0; i < installData.targetApps.length; ++i) {
  3715.         var targetApp = installData.targetApps[i];
  3716.         if (targetApp.id != gApp.ID) {
  3717.         /* XXXben uncomment when this works!
  3718.           var settingsThingy = Components.classes[]
  3719.                                         .getService(Components.interfaces.nsIXULRunnerSettingsThingy);
  3720.           try {
  3721.             var appPrefix = "SOFTWARE\\Mozilla\\XULRunner\\Applications\\";
  3722.             var branch = settingsThingy.getBranch(appPrefix + targetApp.id);
  3723.             var path = branch.getProperty("ExtensionsLocation");
  3724.             var destination = Components.classes["@mozilla.org/file/local;1"]
  3725.                                         .createInstance(nsILocalFile);
  3726.             destination.initWithPath(path);
  3727.             xpiFile.copyTo(file, xpiFile.leafName);
  3728.           }
  3729.           catch (e) {
  3730.           }
  3731.          */
  3732.         } 
  3733.       }        
  3734.     }
  3735.     
  3736.     /**
  3737.      * Extracts and then starts the install for extensions / themes contained
  3738.      * within a xpi.
  3739.      */
  3740.     function installMultiXPI(xpiFile, installData) {
  3741.       var fileURL = getURIFromFile(xpiFile).QueryInterface(nsIURL);
  3742.       if (fileURL.fileExtension.toLowerCase() != "xpi") {
  3743.         LOG("Invalid File Extension: Item: \"" + fileURL.fileName + "\" has an " + 
  3744.             "invalid file extension. Only xpi file extensions are allowed for " +
  3745.             "multiple item packages.");
  3746.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3747.         showMessage("invalidFileExtTitle", [], 
  3748.                     "invalidFileExtMessage", [installData.name,
  3749.                     fileURL.fileExtension,
  3750.                     bundle.GetStringFromName("type-" + installData.type)]);
  3751.         return;
  3752.       }
  3753.  
  3754.       try {
  3755.         var zipReader = getZipReaderForFile(xpiFile);
  3756.       }
  3757.       catch (e) {
  3758.         LOG("installMultiXPI: failed to open xpi file: " + xpiFile.path);
  3759.         throw e;
  3760.       }
  3761.  
  3762.       var searchForEntries = ["*.xpi", "*.jar"];
  3763.       var files = [];
  3764.       for (var i = 0; i < searchForEntries.length; ++i) {
  3765.         var entries = zipReader.findEntries(searchForEntries[i]);
  3766.         while (entries.hasMoreElements()) {
  3767.           var entry = entries.getNext().QueryInterface(Components.interfaces.nsIZipEntry);
  3768.           var target = getFile(KEY_TEMPDIR, [entry.name]);
  3769.           try {
  3770.             target.createUnique(nsILocalFile.NORMAL_FILE_TYPE, PERMS_FILE);
  3771.           }
  3772.           catch (e) {
  3773.             LOG("installMultiXPI: failed to create target file for extraction " +
  3774.                 " file = " + target.path + ", exception = " + e + "\n");
  3775.           }
  3776.           zipReader.extract(entry.name, target);
  3777.           files.push(target);
  3778.         }
  3779.       }
  3780.       zipReader.close();
  3781.  
  3782.       if (files.length == 0) {
  3783.         LOG("Multiple Item Package: Item: \"" + fileURL.fileName + "\" does " +
  3784.             "not contain a valid package to install.");
  3785.         var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  3786.         showMessage("missingPackageFilesTitle",
  3787.                     [bundle.GetStringFromName("type-" + installData.type)],
  3788.                     "missingPackageFilesMessage", [installData.name,
  3789.                     bundle.GetStringFromName("type-" + installData.type)]);
  3790.         return;
  3791.       }
  3792.  
  3793.       // When installing themes we need to open the theme manager if it is not
  3794.       // already opened except when we are installing a xpi dropped into a
  3795.       // extensions directory.
  3796.       fileURL = getURIFromFile(files[files.length - 1]).QueryInterface(nsIURL);
  3797.       if (fileURL.fileExtension.toLowerCase() == "jar") {
  3798.         var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  3799.                            .getService(Components.interfaces.nsIWindowMediator);
  3800.         var win = wm.getMostRecentWindow("Extension:Manager-extensions");
  3801.         if (win) {
  3802.           win = wm.getMostRecentWindow("Extension:Manager-themes");
  3803.           if (win) {
  3804.             win.focus();
  3805.           }
  3806.           else {
  3807.             var themeManagerURL = gPref.getCharPref(PREF_XPINSTALL_STATUS_DLG_SKIN);
  3808.             var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  3809.                                .getService(Components.interfaces.nsIWindowWatcher);
  3810.             ww.openWindow(null, themeManagerURL, 
  3811.                           "", "chrome,centerscreen,titlebar,dialog=no,resizable", []);
  3812.           }
  3813.         }
  3814.       }
  3815.  
  3816.       for (i = 0; i < files.length; ++i) {
  3817.         em.installItemFromFileInternal(files[i], aInstallLocationKey, null);
  3818.         files[i].remove(false);
  3819.       }
  3820.     }
  3821.  
  3822.     /**
  3823.      * An observer for the Extension Update System.
  3824.      * @constructor
  3825.      */
  3826.     function IncompatibleObserver() {}
  3827.     IncompatibleObserver.prototype = {
  3828.       _id: null,
  3829.       _type: nsIUpdateItem.TYPE_ANY,
  3830.       _xpi: null,
  3831.       _installManifest: null,
  3832.       _installRDF: null,
  3833.       
  3834.       /** 
  3835.        * Ask the Extension Update System if there are any version updates for
  3836.        * this item that will allow it to be compatible with this version of 
  3837.        * the Application.
  3838.        * @param   installManifest 
  3839.        *          The Install Manifest datasource for the item.
  3840.        * @param   installData
  3841.        *          The Install Data object for the item.
  3842.        * @param   xpiFile         
  3843.        *          The staged source XPI file that contains the item. Cleaned 
  3844.        *          up by this process.
  3845.        */
  3846.       checkForUpdates: function(installManifest, installData, xpiFile, installRDF) {
  3847.         this._id              = installData.id;
  3848.         this._type            = installData.type;
  3849.         this._xpi             = xpiFile;
  3850.         this._installManifest = installManifest;
  3851.         this._installRDF      = installRDF;
  3852.         
  3853.         var item = makeItem(installData.id, installData.version, 
  3854.                             aInstallLocationKey, 
  3855.                             installData.currentApp.minVersion, 
  3856.                             installData.currentApp.maxVersion,
  3857.                             installData.name,
  3858.                             "", /* XPI Update URL */
  3859.                             "", /* XPI Update Hash */
  3860.                             "", /* Icon URL */
  3861.                             installData.updateURL || "", 
  3862.                             installData.type);
  3863.         em.update([item], 1, true, this);
  3864.       },
  3865.       
  3866.       /**
  3867.        * See nsIExtensionManager.idl
  3868.        */
  3869.       onUpdateStarted: function() {
  3870.         LOG("Phone Home Listener: Update Started");
  3871.         em.datasource.onUpdateStarted();
  3872.       },
  3873.       
  3874.       /**
  3875.        * See nsIExtensionManager.idl
  3876.        */
  3877.       onUpdateEnded: function() {
  3878.         LOG("Phone Home Listener: Update Ended");
  3879.         // We are responsible for cleaning up this file!
  3880.         this._installRDF.remove(false);
  3881.         em.datasource.onUpdateEnded();
  3882.       },
  3883.       
  3884.       /**
  3885.        * See nsIExtensionManager.idl
  3886.        */
  3887.       onAddonUpdateStarted: function(addon) {
  3888.         LOG("Phone Home Listener: Update For " + addon.id + " started");
  3889.         em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path,
  3890.                                                 addon.type, addon.version);
  3891.         em.datasource.onAddonUpdateStarted(addon);
  3892.       },
  3893.       
  3894.       /**
  3895.        * See nsIExtensionManager.idl
  3896.        */
  3897.       onAddonUpdateEnded: function(addon, status) {
  3898.         LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status); 
  3899.         em.datasource.removeDownload(this._xpi.path);
  3900.         LOG("Version Check Phone Home Completed");
  3901.         // Only compatibility updates (e.g. STATUS_VERSIONINFO) are currently
  3902.         // supported
  3903.         if (status == nsIAddonUpdateCheckListener.STATUS_VERSIONINFO) {
  3904.           em.datasource.setTargetApplicationInfo(addon.id, 
  3905.                                                  addon.minAppVersion,
  3906.                                                  addon.maxAppVersion, 
  3907.                                                  this._installManifest);
  3908.  
  3909.           // Try and install again, but use the updated compatibility DB
  3910.           em.installItemFromFileInternal(this._xpi, aInstallLocationKey, 
  3911.                                          this._installManifest);
  3912.  
  3913.           // Add the updated compatibility info to the datasource if done
  3914.           if (StartupCache.entries[aInstallLocationKey][addon.id].op == OP_NONE) {
  3915.             em.datasource.updateTargetAppInfo(addon.id, addon.minAppVersion,
  3916.                                               addon.maxAppVersion);
  3917.           }
  3918.           else { // needs a restart
  3919.             // Add updatedMinVersion and updatedMaxVersion so it can be used
  3920.             // to update the data sources during the installation or upgrade.
  3921.             em.datasource.setUpdatedTargetAppInfo(addon.id, addon.minAppVersion,
  3922.                                                   addon.maxAppVersion);
  3923.           }
  3924.           // Prevent the datasource file from being lazily recreated after
  3925.           // it is deleted by calling Flush.
  3926.           this._installManifest.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource);
  3927.           this._installManifest.Flush();
  3928.         }
  3929.         else {
  3930.           em.datasource.removeDownload(this._xpi.path);
  3931.           showIncompatibleError(installData);
  3932.           // We are responsible for cleaning up this file!
  3933.           InstallLocations.get(aInstallLocationKey).removeFile(this._xpi);
  3934.         }
  3935.         em.datasource.onAddonUpdateEnded(addon, status);
  3936.       },
  3937.  
  3938.       /**
  3939.        * See nsISupports.idl
  3940.        */
  3941.       QueryInterface: function(iid) {
  3942.         if (!iid.equals(Components.interfaces.nsIAddonUpdateCheckListener) &&
  3943.             !iid.equals(Components.interfaces.nsISupports))
  3944.           throw Components.results.NS_ERROR_NO_INTERFACE;
  3945.         return this;
  3946.       }
  3947.     }
  3948.  
  3949.     var installManifestFile = extractRDFFileToTempDir(aXPIFile, FILE_INSTALL_MANIFEST, true);
  3950.     var shouldPhoneHomeIfNecessary = false;
  3951.     if (!aInstallManifest) {
  3952.       // If we were not called with an Install Manifest, we were called from 
  3953.       // some other path than the Phone Home system, so we do want to phone
  3954.       // home if the version is incompatible.
  3955.       shouldPhoneHomeIfNecessary = true;
  3956.       var installManifest = getInstallManifest(installManifestFile);
  3957.       if (!installManifest) {
  3958.         LOG("The Install Manifest supplied by this item is not well-formed. " + 
  3959.             "Installation will not proceed.");
  3960.         installManifestFile.remove(false);
  3961.         return;
  3962.       }
  3963.     }
  3964.     else
  3965.       installManifest = aInstallManifest;
  3966.     
  3967.     var installData = this._getInstallData(installManifest);
  3968.     switch (installData.error) {
  3969.     case INSTALLERROR_INCOMPATIBLE_VERSION:
  3970.       // Since the caller cleans up |aXPIFile|, and we're not yet sure whether or
  3971.       // not we need it (we may need it if a remote version bump that makes it 
  3972.       // compatible is discovered by the call home) - so we must stage it for 
  3973.       // later ourselves.
  3974.       if (shouldPhoneHomeIfNecessary && installData.currentApp) {
  3975.         var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  3976.         if (!installLocation) {
  3977.           installManifestFile.remove(false);
  3978.           return;
  3979.         }
  3980.         var stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  3981.         (new IncompatibleObserver(this)).checkForUpdates(installManifest, 
  3982.                                                          installData, stagedFile,
  3983.                                                          installManifestFile);
  3984.         // Return early to prevent deletion of the install manifest file.
  3985.         return;
  3986.       }
  3987.       else {
  3988.         // XXXben Look up XULRunnerSettingsThingy to see if there is a registered
  3989.         //        app that can handle this item, if so just stage and don't show
  3990.         //        this error!
  3991.         showIncompatibleError(installData);
  3992.       }
  3993.       break;
  3994.     case INSTALLERROR_SUCCESS:
  3995.       // Installation of multiple extensions / themes contained within a single xpi.
  3996.       if (installData.type == nsIUpdateItem.TYPE_MULTI_XPI) {
  3997.         installMultiXPI(aXPIFile, installData);
  3998.         break;
  3999.       }
  4000.  
  4001.       // Stage the extension's XPI so it can be extracted at the next restart.
  4002.       var installLocation = getInstallLocation(installData.id, aInstallLocationKey);
  4003.       if (!installLocation) {
  4004.         // No cleanup of any of the staged XPI files should be required here, 
  4005.         // because this should only ever fail on the first recurse through
  4006.         // this function, BEFORE staging takes place... technically speaking
  4007.         // a location could become readonly during the phone home process, 
  4008.         // but that's an edge case I don't care about.
  4009.         installManifestFile.remove(false);
  4010.         return;
  4011.       }
  4012.  
  4013.       // Stage a copy of the XPI/JAR file for our own evil purposes...
  4014.       stagedFile = installLocation.stageFile(aXPIFile, installData.id);
  4015.       
  4016.       var restartRequired = this.installRequiresRestart(installData.id, 
  4017.                                                         installData.type);
  4018.       // Determine which configuration function to use based on whether or not
  4019.       // there is data about this item in our datasource already - if there is 
  4020.       // we want to upgrade, otherwise we install fresh.
  4021.       var ds = this.datasource;
  4022.       if (installData.id in ds.visibleItems && ds.visibleItems[installData.id]) {
  4023.         // We enter this function if any data corresponding to an existing GUID
  4024.         // is found, regardless of its Install Location. We need to check before
  4025.         // "upgrading" an item that Install Location of the new item is of equal
  4026.         // or higher priority than the old item, to make sure the datasource only
  4027.         // ever tracks metadata for active items.
  4028.         var oldInstallLocation = this.getInstallLocation(installData.id);
  4029.         if (oldInstallLocation.priority >= installLocation.priority) {
  4030.           this._upgradeItem(installManifest, installData.id, installLocation, 
  4031.                             installData.type);
  4032.           if (!restartRequired) {
  4033.             this._finalizeUpgrade(installData.id);
  4034.             this._finalizeInstall(installData.id, stagedFile);
  4035.           }
  4036.         }
  4037.       }
  4038.       else {
  4039.         this._configureForthcomingItem(installManifest, installData.id, 
  4040.                                         installLocation, installData.type);
  4041.         if (!restartRequired)
  4042.           this._finalizeInstall(installData.id, stagedFile);
  4043.       }
  4044.       this._updateManifests(restartRequired);
  4045.       break;
  4046.     case INSTALLERROR_INVALID_GUID:
  4047.       LOG("Invalid GUID: Item has GUID: \"" + installData.id + "\"" + 
  4048.           " which is not well-formed.");
  4049.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4050.       showMessage("incompatibleTitle", 
  4051.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4052.                   "invalidGUIDMessage", [installData.name, installData.id]);
  4053.       break;
  4054.     case INSTALLERROR_INVALID_VERSION:
  4055.       LOG("Invalid Version: Item: \"" + installData.id + "\" has version " + 
  4056.           installData.version + " which is not well-formed.");
  4057.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4058.       showMessage("incompatibleTitle", 
  4059.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4060.                   "invalidVersionMessage", [installData.name, installData.version]);
  4061.       break;
  4062.     case INSTALLERROR_INCOMPATIBLE_PLATFORM:
  4063.       const osABI = gOSTarget + "_" + gXPCOMABI;
  4064.       LOG("Incompatible Platform: Item: \"" + installData.id + "\" is not " + 
  4065.           "compatible with '" + osABI + "'.");
  4066.       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  4067.       showMessage("incompatibleTitle", 
  4068.                   [bundle.GetStringFromName("type-" + installData.type)], 
  4069.                   "incompatiblePlatformMessage",
  4070.                   [installData.name, BundleManager.appName, osABI]);
  4071.       break;
  4072.     default:
  4073.       break;
  4074.     }
  4075.     
  4076.     // Check to see if this item supports other applications and in that case
  4077.     // stage the the XPI file in the location specified by those applications.
  4078.     stageXPIForOtherApps(aXPIFile, installData);
  4079.  
  4080.     installManifestFile.remove(false);
  4081.   },
  4082.   
  4083.   /**
  4084.    * Whether or not this type's installation/uninstallation requires 
  4085.    * the application to be restarted.
  4086.    * @param   id
  4087.    *          The GUID of the item
  4088.    * @param   type
  4089.    *          The nsIUpdateItem type of the item
  4090.    * @returns true if installation of an item of this type requires a 
  4091.    *          restart.
  4092.    */
  4093.   installRequiresRestart: function(id, type) {
  4094.     switch (type) {
  4095.     case nsIUpdateItem.TYPE_EXTENSION:
  4096.       return true;
  4097.     case nsIUpdateItem.TYPE_THEME:
  4098.       var internalName = this.datasource.getItemProperty(id, "internalName");
  4099.       var needsRestart = false;
  4100.       if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
  4101.         needsRestart = internalName == gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
  4102.       if (!needsRestart &&
  4103.           gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN))
  4104.         needsRestart = internalName == gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
  4105.       return needsRestart;
  4106.     }
  4107.     return false;
  4108.   },
  4109.   
  4110.   /**
  4111.    * Perform initial configuration on an item that has just or will be 
  4112.    * installed. This inserts the item into the appropriate container in the
  4113.    * datasource, so that the application UI shows the item even if it will
  4114.    * not actually be installed until the next restart.
  4115.    * @param   installManifest 
  4116.    *          The Install Manifest datasource that describes this item.
  4117.    * @param   id          
  4118.    *          The GUID of this item.
  4119.    * @param   installLocation
  4120.    *          The Install Location where this item is installed.
  4121.    * @param   type
  4122.    *          The nsIUpdateItem type of this item. 
  4123.    */  
  4124.   _configureForthcomingItem: function(installManifest, id, installLocation, type) {
  4125.     var ds = this.datasource;
  4126.     ds.updateVisibleList(id, installLocation.name, false);
  4127.     var props = { name            : EM_L(getManifestProperty(installManifest, "name")),
  4128.                   version         : EM_L(getManifestProperty(installManifest, "version")),
  4129.                   installLocation : EM_L(installLocation.name),
  4130.                   type            : EM_I(type),
  4131.                   availableUpdateURL    : null,
  4132.                   availableUpdateHash   : null,
  4133.                   availableUpdateVersion: null };
  4134.     for (var p in props)
  4135.       ds.setItemProperty(id, EM_R(p), props[p]);
  4136.     ds.updateProperty(id, "displayDescription");
  4137.     ds.updateProperty(id, "availableUpdateURL");
  4138.     
  4139.     this._setOp(id, OP_NEEDS_INSTALL);
  4140.     
  4141.     // Insert it into the child list NOW rather than later because:
  4142.     // - extensions installed using the command line need to be a member
  4143.     //   of a container during the install phase for the code to be able
  4144.     //   to identify profile vs. global
  4145.     // - extensions installed through the UI should show some kind of
  4146.     //   feedback to indicate their presence is forthcoming (i.e. they
  4147.     //   will be available after a restart).
  4148.     ds.insertItemIntoContainer(id);
  4149.     
  4150.     this._notifyAction(id, EM_ITEM_INSTALLED);
  4151.   },
  4152.   
  4153.   /**
  4154.    * Perform configuration on an item that has just or will be upgraded.
  4155.    * @param   installManifest
  4156.    *          The Install Manifest datasource that describes this item.
  4157.    * @param   itemID
  4158.    *          The GUID of this item.
  4159.    * @param   installLocation
  4160.    *          The Install Location where this item is installed.
  4161.    * @param   type
  4162.    *          The nsIUpdateItem type of this item. 
  4163.    */
  4164.   _upgradeItem: function (installManifest, id, installLocation, type) {
  4165.     // Don't change any props that would need to be reset if the install fails.
  4166.     // They will be reset as appropriate by the upgrade/install process.
  4167.     var ds = this.datasource;
  4168.     ds.updateVisibleList(id, installLocation.name, false);
  4169.     var props = { installLocation : EM_L(installLocation.name),
  4170.                   type            : EM_I(type),
  4171.                   availableUpdateURL      : null,
  4172.                   availableUpdateHash     : null,
  4173.                   availableUpdateVersion  : null };
  4174.     for (var p in props)
  4175.       ds.setItemProperty(id, EM_R(p), props[p]);
  4176.     ds.updateProperty(id, "displayDescription");
  4177.     ds.updateProperty(id, "availableUpdateURL");
  4178.  
  4179.     this._setOp(id, OP_NEEDS_UPGRADE);
  4180.     this._notifyAction(id, EM_ITEM_UPGRADED);
  4181.   },
  4182.  
  4183.   /** 
  4184.    * Completes an Extension's installation.
  4185.    * @param   id
  4186.    *          The GUID of the Extension to install.
  4187.    * @param   file
  4188.    *          The XPI/JAR file to install from. If this is null, we try to
  4189.    *          determine the stage file location from the ID.
  4190.    */
  4191.   _finalizeInstall: function(id, file) {
  4192.     var ds = this.datasource;
  4193.     var type = ds.getItemProperty(id, "type");
  4194.     if (id == 0 || id == -1) {
  4195.       ds.removeCorruptItem(id, type);
  4196.       return;
  4197.     }
  4198.     var installLocation = this.getInstallLocation(id);
  4199.     if (!installLocation) {
  4200.       // If the install location is null, that means we've reached the finalize
  4201.       // state without the item ever having metadata added for it, which implies
  4202.       // bogus data in the Startup Cache. Clear the entries and don't do anything
  4203.       // else.
  4204.       var entries = StartupCache.findEntries(id);
  4205.       for (var i = 0; i < entries.length; ++i) {
  4206.         var location = InstallLocations.get(entries[i].location);
  4207.         StartupCache.clearEntry(location, id);
  4208.         PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4209.       }
  4210.       return;
  4211.     }
  4212.     var itemLocation = installLocation.getItemLocation(id);
  4213.  
  4214.     if (!file && "stageFile" in installLocation)
  4215.       file = installLocation.getStageFile(id);
  4216.     
  4217.     // If |file| is null or does not exist, the installer assumes the item is
  4218.     // a dropped-in directory.
  4219.     var installer = new Installer(this.datasource, id, installLocation, type);
  4220.     installer.installFromFile(file);
  4221.  
  4222.     // If the file was staged, we must clean it up ourselves, otherwise the 
  4223.     // EM caller is responsible for doing so (e.g. XPInstall)
  4224.     if (file)
  4225.       installLocation.removeFile(file);
  4226.     
  4227.     // Clear the op flag from the Startup Cache and Pending Operations sets
  4228.     StartupCache.put(installLocation, id, OP_NONE, true);
  4229.     PendingOperations.clearItem(OP_NEEDS_INSTALL, id);
  4230.   },
  4231.  
  4232.   /**
  4233.    * Removes an item's metadata in preparation for an upgrade-install.
  4234.    * @param   id
  4235.    *          The GUID of the item to uninstall.
  4236.    */
  4237.   _finalizeUpgrade: function(id) {
  4238.     // Retrieve the item properties *BEFORE* we clean the resource!
  4239.     var ds = this.datasource;
  4240.     var installLocation = this.getInstallLocation(id);
  4241.  
  4242.     var stagedFile = null;
  4243.     if ("getStageFile" in installLocation)
  4244.       stagedFile = installLocation.getStageFile(id);
  4245.  
  4246.     if (stagedFile)
  4247.       var installRDF = extractRDFFileToTempDir(stagedFile, FILE_INSTALL_MANIFEST, true);
  4248.     else
  4249.       installRDF = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  4250.     if (installRDF.exists()) {
  4251.       var installManifest = getInstallManifest(installRDF);
  4252.       if (installManifest) {
  4253.         var type = getAddonTypeFromInstallManifest(installManifest);
  4254.         var userDisabled = ds.getItemProperty(id, "userDisabled") == "true";
  4255.  
  4256.         // Clean the item resource
  4257.         ds.removeItemMetadata(id);
  4258.         // Now set up the properties on the item to mimic an item in its
  4259.         // "initial state" for installation.
  4260.         this._configureForthcomingItem(installManifest, id, installLocation, 
  4261.                                        type);
  4262.         if (userDisabled)
  4263.           ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  4264.       }
  4265.       if (stagedFile)
  4266.         installRDF.remove(false);
  4267.     }
  4268.     // Clear the op flag from the Pending Operations set. Do NOT clear op flag in 
  4269.     // the startup cache since this may have been reset to OP_NEEDS_INSTALL by
  4270.     // |_configureForthcomingItem|.
  4271.     PendingOperations.clearItem(OP_NEEDS_UPGRADE, id);
  4272.   },
  4273.   
  4274.   /**
  4275.    * Completes an item's uninstallation.
  4276.    * @param   id
  4277.    *          The GUID of the item to uninstall.
  4278.    */
  4279.   _finalizeUninstall: function(id) {
  4280.     var ds = this.datasource;
  4281.     
  4282.     var installLocation = this.getInstallLocation(id);
  4283.     if (!installLocation.itemIsManagedIndependently(id)) {
  4284.       try {
  4285.         // Having a callback that does nothing just causes the directory to be
  4286.         // removed.
  4287.         safeInstallOperation(id, installLocation, 
  4288.                              { data: null, callback: function() { } });
  4289.       }
  4290.       catch (e) {
  4291.         LOG("_finalizeUninstall: failed to remove directory for item: " + id + 
  4292.             " at Install Location: " + installLocation.name + ", rolling back uninstall");
  4293.         // Removal of the files failed, reset the uninstalled flag and rewrite
  4294.         // the install manifests so this item's components are registered.
  4295.         // Clear the op flag from the Startup Cache
  4296.         StartupCache.put(installLocation, id, OP_NONE, true);
  4297.         var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4298.         this._updateManifests(restartRequired);
  4299.         return;
  4300.       }
  4301.     }
  4302.     else if (installLocation.name == KEY_APP_PROFILE ||
  4303.              installLocation.name == KEY_APP_GLOBAL) {
  4304.       // Check for a pointer file and remove it if it exists
  4305.       var pointerFile = installLocation.location.clone();
  4306.       pointerFile.append(id);
  4307.       if (pointerFile.exists() && !pointerFile.isDirectory())
  4308.         pointerFile.remove(false);
  4309.     }
  4310.     
  4311.     // Clean the item resource
  4312.     ds.removeItemMetadata(id);
  4313.     
  4314.     // Do this LAST since inferences are made about an item based on
  4315.     // what container it's in.
  4316.     ds.removeItemFromContainer(id);
  4317.     
  4318.     // Clear the op flag from the Startup Cache and the Pending Operations set.
  4319.     StartupCache.clearEntry(installLocation, id);
  4320.     PendingOperations.clearItem(OP_NEEDS_UNINSTALL, id);
  4321.   },
  4322.   
  4323.   /**
  4324.    * Uninstalls an item. If the uninstallation cannot be performed immediately
  4325.    * it is scheduled for the next restart.
  4326.    * @param   id
  4327.    *          The GUID of the item to uninstall.
  4328.    */
  4329.   uninstallItem: function(id) {
  4330.     var em = this;
  4331.     /**
  4332.      * Cleans up any staged xpis for this item when we uninstall. This fixes 
  4333.      * this scenario:
  4334.      * 1 install Foo 1.0, and restart
  4335.      * 2 install Foo 1.1, 
  4336.      * 3 without restarting, uninstall Foo 1.1
  4337.      * 4 Foo is uninstalled but the staged XPI from the install operation in
  4338.      *   step 2 lingers, and is offered to the user on the next startup to
  4339.      *   be installed.
  4340.      * @param   id
  4341.      *          The GUID of the item to look for staged files for.
  4342.      */
  4343.     function cleanUpPendingStageFile(id) {
  4344.       var foundStageOp = false;
  4345.       var stageOps = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
  4346.       stageOps = stageOps.concat(PendingOperations.getOperations(OP_NEEDS_INSTALL));
  4347.       for (var i = 0; i < stageOps.length; ++i) {
  4348.         if (stageOps[i].id == id) {
  4349.           foundStageOp = true;
  4350.           break;
  4351.         }
  4352.       }
  4353.       
  4354.       if (foundStageOp) {
  4355.         var location = em.getInstallLocation(id);
  4356.         var stageFile = location.getStageFile(id);
  4357.         if (stageFile)
  4358.           location.removeFile(stageFile);
  4359.       }
  4360.     }
  4361.   
  4362.     var ds = this.datasource;
  4363.     var type = ds.getItemProperty(id, "type");
  4364.     if (!ds.isDownloadItem(id)) {
  4365.       cleanUpPendingStageFile(id);
  4366.       this._setOp(id, OP_NEEDS_UNINSTALL);
  4367.       var restartRequired = this.installRequiresRestart(id, type);
  4368.       if (!restartRequired) {
  4369.         this._finalizeUninstall(id);
  4370.         this._updateManifests(restartRequired);
  4371.       }
  4372.     }
  4373.     else {
  4374.       // Bad download entry - uri is url, e.g. "http://www.foo.com/test.xpi"
  4375.       // ... just remove it from the list. 
  4376.       ds.removeCorruptDLItem(id);
  4377.     }
  4378.     
  4379.     this._notifyAction(id, EM_ITEM_UNINSTALLED);
  4380.   },
  4381.  
  4382.   /**
  4383.    * Sets the pending operation for a visible item. 
  4384.    * @param   id
  4385.    *          The GUID of the item
  4386.    * @param   op
  4387.    *          The name of the operation to be performed
  4388.    */  
  4389.   _setOp: function(id, op) {
  4390.     var location = this.getInstallLocation(id);
  4391.     StartupCache.put(location, id, op, true);
  4392.     PendingOperations.addItem(op, { locationKey: location.name, id: id });
  4393.     var ds = this.datasource;
  4394.     ds.updateProperty(id, "opType");
  4395.     ds.updateProperty(id, "updateable");
  4396.     ds.updateProperty(id, "displayDescription");
  4397.     var restartRequired = this.installRequiresRestart(id, ds.getItemProperty(id, "type"))
  4398.     this._updateManifests(restartRequired);
  4399.   },
  4400.   
  4401.   /**
  4402.    * Enables an item for the application (e.g. the item meets all requirements
  4403.    * for it to be enabled). If the item is not disabled by the user this will
  4404.    * also set the needs-enable operation for the next restart.
  4405.    * @param   id
  4406.    *          The ID of the item to be enabled by the application.
  4407.    */
  4408.   _appEnableItem: function(id) {
  4409.     var ds = this.datasource;
  4410.     if (ds.getItemProperty(id, "userDisabled") != "true") {
  4411.       this._setOp(id, OP_NEEDS_ENABLE);
  4412.       this._notifyAction(id, EM_ITEM_ENABLED);
  4413.     }
  4414.     else {
  4415.       ds.setItemProperty(id, EM_R("appDisabled"), null);
  4416.       ds.updateProperty(id, "compatible");
  4417.       ds.updateProperty(id, "displayDescription");
  4418.     }
  4419.   },
  4420.  
  4421.   /**
  4422.    * Disables an item for the application (e.g. the item does not meets all
  4423.    * requirements like app compatibility for it to be enabled). If the item is
  4424.    * not disabled by the user this will also set the needs-disable operation
  4425.    * for the next restart.
  4426.    * @param   id
  4427.    *          The ID of the item to be disabled by the application.
  4428.    */
  4429.   _appDisableItem: function(id) {
  4430.     var ds = this.datasource;
  4431.     ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  4432.     if (ds.getItemProperty(id, "userDisabled") != "true") {
  4433.       this._setOp(id, OP_NEEDS_DISABLE);
  4434.       this._notifyAction(id, EM_ITEM_DISABLED);
  4435.     }
  4436.   },
  4437.     
  4438.   /**
  4439.    * Sets an item to be enabled by the user. If the item is already enabled this
  4440.    * clears the needs-enable operation for the next restart. If the item's
  4441.    * operation is set to needs-uninstall this will cancel the uninstall and set
  4442.    * the needs-enable operation for the next restart if the item is disabled.
  4443.    * 
  4444.    * @param   id
  4445.    *          The ID of the item to be enabled by the user.
  4446.    */
  4447.   enableItem: function(id) {
  4448.     var ds = this.datasource;
  4449.     if (ds.getItemProperty(id, "userDisabled") == "true" ||
  4450.         ds.getItemProperty(id, "appDisabled") == "true" &&
  4451.         ds.getItemProperty(id, "compatible") == "true") {
  4452.       this._setOp(id, OP_NEEDS_ENABLE);
  4453.       this._notifyAction(id, EM_ITEM_ENABLED);
  4454.     }
  4455.     else {
  4456.       this._setOp(id, OP_NONE);
  4457.       this._notifyAction(id, EM_ITEM_CANCEL);
  4458.     }
  4459.   },
  4460.   
  4461.   /**
  4462.    * Sets an item to be disabled by the user. If the item is already disabled
  4463.    * this clears the needs-disable operation for the next restart.
  4464.    * @param   id
  4465.    *          The ID of the item to be disabled by the user.
  4466.    */
  4467.   disableItem: function(id) {
  4468.     var ds = this.datasource;
  4469.     if (ds.getItemProperty(id, "userDisabled") == "true") {
  4470.       this._setOp(id, OP_NONE);
  4471.       this._notifyAction(id, EM_ITEM_CANCEL);
  4472.     }
  4473.     else {
  4474.       this._setOp(id, OP_NEEDS_DISABLE);
  4475.       this._notifyAction(id, EM_ITEM_DISABLED);
  4476.     }
  4477.   },
  4478.   
  4479.   /**
  4480.    * Notify observers of a change to an item that has been requested by the
  4481.    * user. 
  4482.    */
  4483.   _notifyAction: function(id, reason) {
  4484.     gOS.notifyObservers(this.datasource.getItemForID(id), 
  4485.                         EM_ACTION_REQUESTED_TOPIC, reason);
  4486.   },
  4487.   
  4488.   /**
  4489.    * See nsIExtensionManager.idl
  4490.    */
  4491.   update: function(items, itemCount, versionUpdateOnly, listener) {
  4492.     var appID = gApp.ID;
  4493.     var appVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  4494.                              gApp.version);
  4495.  
  4496.     if (items.length == 0)
  4497.       items = this.getItemList(nsIUpdateItem.TYPE_ADDON, { });
  4498.  
  4499.     var updater = new ExtensionItemUpdater(appID, appVersion, this);
  4500.     updater.checkForUpdates(items, items.length, versionUpdateOnly, listener);
  4501.   },
  4502.  
  4503.   /**
  4504.    * @returns An enumeration of all registered Install Locations.
  4505.    */
  4506.   get installLocations () {
  4507.     return InstallLocations.enumeration;
  4508.   },
  4509.   
  4510.   /**
  4511.    * Gets the Install Location where a visible Item is stored.
  4512.    * @param   id
  4513.    *          The GUID of the item to locate an Install Location for.
  4514.    * @returns The Install Location object where the item is stored.
  4515.    */
  4516.   getInstallLocation: function(id) {
  4517.     var key = this.datasource.visibleItems[id];
  4518.     return key ? InstallLocations.get(this.datasource.visibleItems[id]) : null;
  4519.   },
  4520.   
  4521.   /**
  4522.    * Gets a nsIUpdateItem for the item with the specified id.
  4523.    * @param   id
  4524.    *          The GUID of the item to construct a nsIUpdateItem for.
  4525.    * @returns The nsIUpdateItem representing the item.
  4526.    */
  4527.   getItemForID: function(id) {
  4528.     return this.datasource.getItemForID(id);
  4529.   },
  4530.   
  4531.   /**
  4532.    * Retrieves a list of nsIUpdateItems of items matching the specified type.
  4533.    * @param   type
  4534.    *          The type of item to return.
  4535.    * @param   countRef
  4536.    *          The XPCJS reference to the number of items returned.
  4537.    * @returns An array of nsIUpdateItems matching the id/type filter.
  4538.    */
  4539.   getItemList: function(type, countRef) {
  4540.     return this.datasource.getItemList(type, countRef);
  4541.   },
  4542.  
  4543.   /**  
  4544.    * See nsIExtensionManager.idl
  4545.    */
  4546.   getIncompatibleItemList: function(id, version, type, includeDisabled, 
  4547.                                     countRef) {
  4548.     var items = this.datasource.getIncompatibleItemList(id, version ? version : undefined,
  4549.                                                         type, includeDisabled);
  4550.     countRef.value = items.length;
  4551.     return items;
  4552.   },
  4553.   
  4554.   /**
  4555.    * Move an Item to the index of another item in its container.
  4556.    * @param   movingID
  4557.    *          The ID of the item to be moved.
  4558.    * @param   destinationID
  4559.    *          The ID of an item to move another item to.
  4560.    */
  4561.   moveToIndexOf: function(movingID, destinationID) {
  4562.     this.datasource.moveToIndexOf(movingID, destinationID);
  4563.   },
  4564.  
  4565.   /////////////////////////////////////////////////////////////////////////////    
  4566.   // Downloads
  4567.   _transactions: [],
  4568.   _downloadCount: 0,
  4569.   
  4570.   /**
  4571.    * Ask the user if they really want to quit the application, since this will 
  4572.    * cancel one or more Extension/Theme downloads.
  4573.    * @param   subject
  4574.    *          A nsISupportsPRBool which this function sets to false if the user
  4575.    *          wishes to cancel all active downloads and quit the application,
  4576.    *          false otherwise.
  4577.    */
  4578.   _confirmCancelDownloadsOnQuit: function(subject) {
  4579.     if (this._downloadCount > 0) {
  4580.       var result;
  4581. //@line 4667 "/build/buildd/mozilla-thunderbird-1.5.0.10/build-dir/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4582.       result = this._confirmCancelDownloads(this._downloadCount, 
  4583.                                             "quitCancelDownloadsAlertTitle",
  4584.                                             "quitCancelDownloadsAlertMsgMultiple",
  4585.                                             "quitCancelDownloadsAlertMsg",
  4586.                                             "dontQuitButtonWin");
  4587. //@line 4679 "/build/buildd/mozilla-thunderbird-1.5.0.10/build-dir/mozilla/toolkit/mozapps/extensions/src/nsExtensionManager.js.in"
  4588.       if (!result)
  4589.         this._cancelDownloads();
  4590.       if (subject instanceof Components.interfaces.nsISupportsPRBool)
  4591.         subject.data = result;
  4592.     }
  4593.   },
  4594.   
  4595.   /**
  4596.    * Ask the user if they really want to go offline, since this will cancel 
  4597.    * one or more Extension/Theme downloads.
  4598.    * @param   subject
  4599.    *          A nsISupportsPRBool which this function sets to false if the user
  4600.    *          wishes to cancel all active downloads and go offline, false
  4601.    *          otherwise.
  4602.    */
  4603.   _confirmCancelDownloadsOnOffline: function(subject) {
  4604.     if (this._downloadCount > 0) {
  4605.       result = this._confirmCancelDownloads(this._downloadCount,
  4606.                                             "offlineCancelDownloadsAlertTitle",
  4607.                                             "offlineCancelDownloadsAlertMsgMultiple",
  4608.                                             "offlineCancelDownloadsAlertMsg",
  4609.                                             "dontGoOfflineButton");
  4610.       if (!result)
  4611.         this._cancelDownloads();
  4612.       var PRBool = subject.QueryInterface(Components.interfaces.nsISupportsPRBool);
  4613.       PRBool.data = result;
  4614.     }
  4615.   },
  4616.   
  4617.   /**
  4618.    * Cancels all active downloads and removes them from the applicable UI.
  4619.    */
  4620.   _cancelDownloads: function() {
  4621.     for (var i = 0; i < this._transactions.length; ++i)
  4622.       gOS.notifyObservers(this._transactions[i], "xpinstall-progress", "cancel");
  4623.     gOS.removeObserver(this, "offline-requested");
  4624.     gOS.removeObserver(this, "quit-application-requested");
  4625.  
  4626.     this._removeAllDownloads();
  4627.   },
  4628.  
  4629.   /**
  4630.    * Ask the user whether or not they wish to cancel the Extension/Theme
  4631.    * downloads which are currently under way.
  4632.    * @param   count
  4633.    *          The number of active downloads.
  4634.    * @param   title
  4635.    *          The key of the title for the message box to be displayed
  4636.    * @param   cancelMessageMultiple
  4637.    *          The key of the message to be displayed in the message box
  4638.    *          when there are > 1 active downloads.
  4639.    * @param   cancelMessageSingle
  4640.    *          The key of the message to be displayed in the message box
  4641.    *          when there is just one active download.
  4642.    * @param   dontCancelButton
  4643.    *          The key of the label to be displayed on the "Don't Cancel 
  4644.    *          Downloads" button.
  4645.    */
  4646.   _confirmCancelDownloads: function(count, title, cancelMessageMultiple, 
  4647.                                     cancelMessageSingle, dontCancelButton) {
  4648.     var bundle = BundleManager.getBundle(URI_DOWNLOADS_PROPERTIES);
  4649.     var title = bundle.GetStringFromName(title);
  4650.     var message, quitButton;
  4651.     if (count > 1) {
  4652.       message = bundle.formatStringFromName(cancelMessageMultiple, [count], 1);
  4653.       quitButton = bundle.formatStringFromName("cancelDownloadsOKTextMultiple", [count], 1);
  4654.     }
  4655.     else {
  4656.       message = bundle.GetStringFromName(cancelMessageSingle);
  4657.       quitButton = bundle.GetStringFromName("cancelDownloadsOKText");
  4658.     }
  4659.     var dontQuitButton = bundle.GetStringFromName(dontCancelButton);
  4660.     
  4661.     var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
  4662.                        .getService(Components.interfaces.nsIWindowMediator);
  4663.     var win = wm.getMostRecentWindow("Extension:Manager");
  4664.     const nsIPromptService = Components.interfaces.nsIPromptService;
  4665.     var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  4666.                        .getService(nsIPromptService);
  4667.     var flags = (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_0) +
  4668.                 (nsIPromptService.BUTTON_TITLE_IS_STRING * nsIPromptService.BUTTON_POS_1);
  4669.     var rv = ps.confirmEx(win, title, message, flags, quitButton, dontQuitButton, null, null, { });
  4670.     return rv == 1;
  4671.   },
  4672.   
  4673.   /** 
  4674.    * Adds a set of Item Downloads to the Manager and starts the download
  4675.    * operation.
  4676.    * @param   items
  4677.    *          An array of nsIUpdateItems to begin downlading.
  4678.    * @param   itemCount
  4679.    *          The length of |items|
  4680.    * @param   fromChrome
  4681.    *          true when called from chrome
  4682.    *          false when not called from chrome (e.g. web page)
  4683.    */
  4684.   addDownloads: function(items, itemCount, fromChrome) { 
  4685.     this._downloadCount += itemCount;
  4686.     
  4687.     var urls = [];
  4688.     var hashes = [];
  4689.     var txn = new ItemDownloadTransaction(this);
  4690.     for (var i = 0; i < itemCount; ++i) {
  4691.       var currItem = items[i];
  4692.       var txnID = Math.round(Math.random() * 100);
  4693.       txn.addDownload(currItem, txnID);
  4694.       this._transactions.push(txn);
  4695.       urls.push(currItem.xpiURL);
  4696.       hashes.push(currItem.xpiHash ? currItem.xpiHash : null);
  4697.     }
  4698.  
  4699.     // Kick off the download process for this transaction
  4700.     gOS.addObserver(this, "offline-requested", false);
  4701.     gOS.addObserver(this, "quit-application-requested", false);
  4702.     
  4703.     if (fromChrome) {
  4704.       // Initiate an install from chrome
  4705.       var xpimgr = 
  4706.           Components.classes["@mozilla.org/xpinstall/install-manager;1"].
  4707.           createInstance(Components.interfaces.nsIXPInstallManager);
  4708.       xpimgr.initManagerWithHashes(urls, hashes, urls.length, txn);
  4709.     }
  4710.     else
  4711.       gOS.notifyObservers(txn, "xpinstall-progress", "open");
  4712.   },
  4713.   
  4714.   /**
  4715.    * Removes a download of a URL.
  4716.    * @param   url
  4717.    *          The URL of the item being downloaded to remove.
  4718.    */
  4719.   removeDownload: function(url) {
  4720.     for (var i = 0; i < this._transactions.length; ++i) {
  4721.       if (this._transactions[i].containsURL(url)) {
  4722.         this._transactions[i].removeDownload(url);
  4723.         return;
  4724.       }
  4725.     } 
  4726.   },
  4727.   
  4728.   /**
  4729.    * Remove all downloads from all transactions.
  4730.    */
  4731.   _removeAllDownloads: function() {
  4732.     for (var i = 0; i < this._transactions.length; ++i)
  4733.       this._transactions[i].removeAllDownloads();
  4734.   },
  4735.  
  4736.   /**
  4737.    * Download Operation State has changed from one to another. 
  4738.    * 
  4739.    * The nsIXPIProgressDialog implementation in the download transaction object
  4740.    * forwards notifications through these methods which we then pass on to any
  4741.    * front end objects implementing nsIExtensionDownloadListener that 
  4742.    * are listening. We maintain the master state of download operations HERE, 
  4743.    * not in the front end, because if the user closes the extension or theme 
  4744.    * managers during the downloads we need to maintain state and not terminate
  4745.    * the download/install process. 
  4746.    *
  4747.    * @param   transaction
  4748.    *          The ItemDownloadTransaction object receiving the download 
  4749.    *          notifications from XPInstall.
  4750.    * @param   addon
  4751.    *          An object representing nsIUpdateItem for the addon being updated
  4752.    * @param   state
  4753.    *          The state we are entering
  4754.    * @param   value
  4755.    *          ???
  4756.    */
  4757.   onStateChange: function(transaction, addon, state, value) {
  4758.     var url = addon.xpiURL;
  4759.     if (!(url in this._progressData)) 
  4760.       this._progressData[url] = { };
  4761.     this._progressData[url].state = state;
  4762.     
  4763.     for (var i = 0; i < this._updateListeners.length; ++i)
  4764.       this._updateListeners[i].onStateChange(addon, state, value);
  4765.  
  4766.     const nsIXPIProgressDialog = Components.interfaces.nsIXPIProgressDialog;
  4767.     switch (state) {
  4768.     case nsIXPIProgressDialog.INSTALL_DONE:
  4769.       --this._downloadCount;
  4770.       break;
  4771.     case nsIXPIProgressDialog.DIALOG_CLOSE:
  4772.       for (var i = 0; i < this._transactions.length; ++i) {
  4773.         if (this._transactions[i].id == transaction.id) {
  4774.           this._transactions.splice(i, 1);
  4775.           delete transaction;
  4776.           break;
  4777.         }
  4778.       }
  4779.       break;
  4780.     }
  4781.     // If we're updating an installed item for which content is already built,
  4782.     // update the "displayDescription" property so the restart now message is
  4783.     // shown.
  4784.     if (addon.id != addon.xpiURL) {
  4785.       var ds = this.datasource;
  4786.       ds.updateProperty(addon.id, "displayDescription");
  4787.       ds.updateProperty(addon.id, "availableUpdateURL");
  4788.       ds.updateProperty(addon.id, "updateable"); 
  4789.     }
  4790.   },
  4791.   
  4792.   _progressData: { },
  4793.   onProgress: function(addon, value, maxValue) {
  4794.     for (var i = 0; i < this._updateListeners.length; ++i)
  4795.       this._updateListeners[i].onProgress(addon, value, maxValue);
  4796.     
  4797.     var url = addon.xpiURL;
  4798.     if (!(url in this._progressData)) 
  4799.       this._progressData[url] = { };
  4800.     this._progressData[url].progress = Math.round((value / maxValue) * 100);
  4801.   },
  4802.  
  4803.   _updateListeners: [],
  4804.   addUpdateListener: function(listener) {
  4805.     for (var i = 0; i < this._updateListeners.length; ++i) {
  4806.       if (this._updateListeners[i] == listener)
  4807.         return i;
  4808.     }
  4809.     this._updateListeners.push(listener);
  4810.     return this._updateListeners.length - 1;
  4811.   },
  4812.   
  4813.   removeUpdateListenerAt: function(index) {
  4814.     this._updateListeners.splice(index, 1);
  4815.     if (this._downloadCount != 0)
  4816.       this.datasource.flushProgressInfo(this._progressData);
  4817.   },
  4818.  
  4819.   /**
  4820.    * The Extensions RDF Datasource
  4821.    */
  4822.   _ds: null,
  4823.  
  4824.   /** 
  4825.    * Loads the Extensions Datasource. This should not be called unless: 
  4826.    * - a piece of Extensions UI is being shown, or
  4827.    * - on startup and there has been a change to an Install Location
  4828.    * ... it should NOT be called on every startup!
  4829.    */
  4830.   _ensureDS: function() {
  4831.     if (!this._ds) {
  4832.       this._ds = new ExtensionsDataSource(this);
  4833.       if (this._ds)
  4834.         this._ds.loadExtensions();
  4835.     }
  4836.   },
  4837.  
  4838.   /**
  4839.    * See nsIExtensionManager.idl
  4840.    */
  4841.   get datasource() {
  4842.     this._ensureDS();
  4843.     return this._ds.QueryInterface(Components.interfaces.nsIRDFDataSource);
  4844.   },
  4845.   
  4846.   /**
  4847.    * See nsIClassInfo.idl
  4848.    */
  4849.   getInterfaces: function(count) {
  4850.     var interfaces = [Components.interfaces.nsIExtensionManager,
  4851.                       Components.interfaces.nsIXPIProgressDialog,
  4852.                       Components.interfaces.nsIObserver];
  4853.     count.value = interfaces.length;
  4854.     return interfaces;
  4855.   },
  4856.   getHelperForLanguage: function(language) { 
  4857.     return null;
  4858.   },
  4859.   get contractID() {
  4860.     return "@mozilla.org/extensions/manager;1";
  4861.   },
  4862.   get classDescription() {
  4863.     return "Extension Manager";
  4864.   },
  4865.   get classID() {
  4866.     return Components.ID("{8A115FAA-7DCB-4e8f-979B-5F53472F51CF}");
  4867.   },
  4868.   get implementationLanguage() {
  4869.     return Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT;
  4870.   },
  4871.   get flags() {
  4872.     return Components.interfaces.nsIClassInfo.SINGLETON;
  4873.   },
  4874.  
  4875.   /**
  4876.    * See nsISupports.idl
  4877.    */
  4878.   QueryInterface: function(iid) {
  4879.     if (!iid.equals(Components.interfaces.nsIExtensionManager) &&
  4880.         !iid.equals(Components.interfaces.nsITimerCallback) &&
  4881.         !iid.equals(Components.interfaces.nsIObserver) &&
  4882.         !iid.equals(Components.interfaces.nsISupports))
  4883.       throw Components.results.NS_ERROR_NO_INTERFACE;
  4884.     return this;
  4885.   }
  4886. };
  4887.  
  4888. /**
  4889.  * This object implements nsIXPIProgressDialog and represents a collection of
  4890.  * XPI/JAR download and install operations. There is one 
  4891.  * ItemDownloadTransaction per back-end XPInstallManager object. We maintain
  4892.  * a collection of separate transaction objects because it's possible to have
  4893.  * multiple separate XPInstall download/install operations going on 
  4894.  * simultaneously, each with its own XPInstallManager instance. For instance
  4895.  * you could start downloading two extensions and then download a theme. Each
  4896.  * of these operations would open the appropriate FE and have to be able to
  4897.  * track each operation independently.
  4898.  * 
  4899.  * @constructor
  4900.  */
  4901. function ItemDownloadTransaction(manager) {
  4902.   this._manager = manager;
  4903.   this._downloads = [];
  4904. }
  4905. ItemDownloadTransaction.prototype = {
  4906.   _manager    : null,
  4907.   _downloads  : [],
  4908.   id          : -1,
  4909.   
  4910.   /**
  4911.    * Add a download to this transaction
  4912.    * @param   addon
  4913.    *          An object implementing nsIUpdateItem for the item to be downloaded
  4914.    * @param   id
  4915.    *          The integer identifier of this transaction
  4916.    */
  4917.   addDownload: function(addon, id) {
  4918.     this._downloads.push({ addon: addon, waiting: true });
  4919.     this._manager.datasource.addDownload(addon);
  4920.     this.id = id;
  4921.   },
  4922.   
  4923.   /**
  4924.    * Removes a download from this transaction
  4925.    * @param   url
  4926.    *          The URL to remove
  4927.    */
  4928.   removeDownload: function(url) {
  4929.     this._manager.datasource.removeDownload(url);
  4930.   },
  4931.   
  4932.   /**
  4933.    * Remove all downloads from this transaction
  4934.    */
  4935.   removeAllDownloads: function() {
  4936.     for (var i = 0; i < this._downloads.length; ++i) {
  4937.       var addon = this._downloads[i].addon;
  4938.       this.removeDownload(addon.xpiURL, addon.type);
  4939.     }
  4940.   },
  4941.   
  4942.   /**
  4943.    * Determine if this transaction is handling the download of a url.
  4944.    * @param   url
  4945.    *          The URL to look for
  4946.    * @returns true if this transaction is downloading the supplied url.
  4947.    */
  4948.   containsURL: function(url) {
  4949.     for (var i = 0; i < this._downloads.length; ++i) {
  4950.       if (this._downloads[i].addon.xpiURL == url)
  4951.         return true;
  4952.     }
  4953.     return false;
  4954.   },
  4955.  
  4956.   /**
  4957.    * See nsIXPIProgressDialog.idl
  4958.    */
  4959.   onStateChange: function(index, state, value) {
  4960.     this._manager.onStateChange(this, this._downloads[index].addon, 
  4961.                                 state, value);
  4962.   },
  4963.   
  4964.   /**
  4965.    * See nsIXPIProgressDialog.idl
  4966.    */
  4967.   onProgress: function(index, value, maxValue) { 
  4968.     this._manager.onProgress(this._downloads[index].addon, value, maxValue);
  4969.   },
  4970.   
  4971.   /////////////////////////////////////////////////////////////////////////////
  4972.   // nsISupports
  4973.   QueryInterface: function(iid) {
  4974.     if (!iid.equals(Components.interfaces.nsIXPIProgressDialog) &&
  4975.         !iid.equals(Components.interfaces.nsISupports))
  4976.       throw Components.results.NS_ERROR_NO_INTERFACE;
  4977.     return this;
  4978.   }
  4979. };
  4980.  
  4981. /**
  4982.  * A listener object to the update check process that routes notifications to
  4983.  * the right places and keeps the datasource up to date.
  4984.  */
  4985. function AddonUpdateCheckListener(listener, datasource) {
  4986.   this._listener = listener;
  4987.   this._ds = datasource;
  4988. }
  4989. AddonUpdateCheckListener.prototype = {
  4990.   _listener: null,
  4991.   _ds: null,
  4992.   
  4993.   onUpdateStarted: function() {
  4994.     if (this._listener)
  4995.       this._listener.onUpdateStarted();
  4996.     this._ds.onUpdateStarted();
  4997.   },
  4998.   
  4999.   onUpdateEnded: function() {
  5000.     if (this._listener)
  5001.       this._listener.onUpdateEnded();
  5002.     this._ds.onUpdateEnded();
  5003.   },
  5004.   
  5005.   onAddonUpdateStarted: function(addon) {
  5006.     if (this._listener)
  5007.       this._listener.onAddonUpdateStarted(addon);
  5008.     this._ds.onAddonUpdateStarted(addon);
  5009.   },
  5010.   
  5011.   onAddonUpdateEnded: function(addon, status) {
  5012.     if (this._listener)
  5013.       this._listener.onAddonUpdateEnded(addon, status);
  5014.     this._ds.onAddonUpdateEnded(addon, status);
  5015.   }
  5016. };
  5017.  
  5018. ///////////////////////////////////////////////////////////////////////////////
  5019. //
  5020. // ExtensionItemUpdater
  5021. //
  5022. function ExtensionItemUpdater(aTargetAppID, aTargetAppVersion, aEM) 
  5023. {
  5024.   this._appID = aTargetAppID;
  5025.   this._appVersion = aTargetAppVersion;
  5026.   this._emDS = aEM._ds;
  5027.   this._em = aEM;
  5028.  
  5029.   getVersionChecker();
  5030. }
  5031.  
  5032. ExtensionItemUpdater.prototype = {
  5033.   _appID              : "",
  5034.   _appVersion         : "",
  5035.   _emDS               : null,
  5036.   _em                 : null,
  5037.   _versionUpdateOnly  : 0,
  5038.   _items              : [],
  5039.   _listener           : null,
  5040.   
  5041.   /////////////////////////////////////////////////////////////////////////////
  5042.   // ExtensionItemUpdater
  5043.   //
  5044.   // When we check for updates to an item, there are two pieces of information
  5045.   // that are returned - 1) info about the newest available version, if any,
  5046.   // and 2) info about the currently installed version. The latter is provided
  5047.   // primarily to inform the client of changes to the application compatibility 
  5048.   // metadata for the current item. Depending on the situation, either 2 or 
  5049.   // 1&2 may be what is required.
  5050.   //
  5051.   // Callers:
  5052.   //  1 - nsUpdateService.js, user event
  5053.   //      User clicked on the update icon to invoke an update check, 
  5054.   //      user clicked on an Extension/Theme and clicked "Update". In this
  5055.   //      case we want to update compatibility metadata about the installed
  5056.   //      version, and look for newer versions to offer. 
  5057.   //  2 - nsUpdateService.js, background event
  5058.   //      Timer fired, background update is being performed. In this case
  5059.   //      we also want to update compatibility metadata and look for newer
  5060.   //      versions.
  5061.   //  3 - Mismatch
  5062.   //      User upgraded to a newer version of the app, update compatibility
  5063.   //      metadata and look for newer versions.
  5064.   //  4 - Install Phone Home
  5065.   //      User installed an item that was deemed incompatible based only
  5066.   //      on the information provided in the item's install.rdf manifest, 
  5067.   //      we look ONLY for compatibility updates in this case to determine
  5068.   //      whether or not the item can be installed.
  5069.   //  
  5070.   checkForUpdates: function(aItems, aItemCount, aVersionUpdateOnly, 
  5071.                             aListener) {
  5072.     this._listener = new AddonUpdateCheckListener(aListener, this._emDS);
  5073.     if (this._listener)
  5074.       this._listener.onUpdateStarted();
  5075.     this._versionUpdateOnly = aVersionUpdateOnly;
  5076.     this._items = aItems;
  5077.     this._responseCount = aItemCount;
  5078.     
  5079.     // This is the number of extensions/themes/etc that we found updates for.
  5080.     this._updateCount = 0;
  5081.  
  5082.     for (var i = 0; i < aItemCount; ++i) {
  5083.       var e = this._items[i];
  5084.       if (this._listener)
  5085.         this._listener.onAddonUpdateStarted(e);
  5086.       (new RDFItemUpdater(this)).checkForUpdates(e, aVersionUpdateOnly);
  5087.     }
  5088.   },
  5089.   
  5090.   /////////////////////////////////////////////////////////////////////////////
  5091.   // ExtensionItemUpdater
  5092.   _applyVersionUpdates: function(aLocalItem, aRemoteItem) {
  5093.     var targetAppInfo = this._emDS.getTargetApplicationInfo(aLocalItem.id, this._emDS);
  5094.     // If targetAppInfo is null this is for a new install. If the local item's
  5095.     // maxVersion does not equal the targetAppInfo maxVersion then this is for
  5096.     // an upgrade. In both of these cases return true if the remotely specified
  5097.     // maxVersion is greater than the local item's maxVersion.
  5098.     if (!targetAppInfo ||
  5099.         gVersionChecker.compare(aLocalItem.maxAppVersion, targetAppInfo.maxVersion) != 0) {
  5100.       if (gVersionChecker.compare(aLocalItem.maxAppVersion, aRemoteItem.maxAppVersion) < 0)
  5101.         return true;
  5102.       else
  5103.         return false;
  5104.     }
  5105.  
  5106.     if (gVersionChecker.compare(targetAppInfo.maxVersion, aRemoteItem.maxAppVersion) < 0) {
  5107.       // Remotely specified maxVersion is newer than the maxVersion 
  5108.       // for the installed Extension. Apply that change to the datasources.
  5109.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  5110.                                      aRemoteItem.maxAppVersion);
  5111.  
  5112.       // If we got here through |checkForMismatches|, this extension has
  5113.       // already been disabled, re-enable it.
  5114.       var op = StartupCache.entries[aLocalItem.installLocationKey][aLocalItem.id].op;
  5115.       if (op == OP_NEEDS_DISABLE ||
  5116.           this._emDS.getItemProperty(aLocalItem.id, "appDisabled") == "true")
  5117.         this._em._appEnableItem(aLocalItem.id);
  5118.       return true;
  5119.     }
  5120.     else if (this._versionUpdateOnly == 2)
  5121.       this._emDS.updateTargetAppInfo(aLocalItem.id, aRemoteItem.minAppVersion,
  5122.                                      aRemoteItem.maxAppVersion);
  5123.     return false;
  5124.   },
  5125.   
  5126.   _isValidUpdate: function(aLocalItem, aRemoteItem) {
  5127.     var appExtensionsVersion =
  5128.       getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION, gApp.version);
  5129.  
  5130.     // Check if the update will only run on a newer version of Firefox. 
  5131.     if (aRemoteItem.minAppVersion && 
  5132.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.minAppVersion) < 0) 
  5133.       return false;
  5134.  
  5135.     // Check if the update will only run on an older version of Firefox. 
  5136.     if (aRemoteItem.maxAppVersion && 
  5137.         gVersionChecker.compare(appExtensionsVersion, aRemoteItem.maxAppVersion) > 0) 
  5138.       return false;
  5139.     
  5140.     return true;
  5141.   },
  5142.   
  5143.   checkForDone: function(item, status) {
  5144.     if (this._listener) {
  5145.       try {
  5146.         this._listener.onAddonUpdateEnded(item, status);
  5147.       }
  5148.       catch (e) {
  5149.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onAddonUpdateEnded: " + e);
  5150.       }
  5151.     }
  5152.     if (--this._responseCount == 0 && this._listener) {
  5153.       try {
  5154.         this._listener.onUpdateEnded();
  5155.       }
  5156.       catch (e) {
  5157.         LOG("ExtensionItemUpdater:checkForDone: Failure in listener's onUpdateEnded: " + e);
  5158.       }
  5159.     }
  5160.   },
  5161. };
  5162.  
  5163. function RDFItemUpdater(aUpdater) {
  5164.   this._updater = aUpdater;
  5165. }
  5166.  
  5167. RDFItemUpdater.prototype = {
  5168.   _updater            : null,
  5169.   _versionUpdateOnly  : 0,
  5170.   _item               : null,
  5171.   
  5172.   checkForUpdates: function(aItem, aVersionUpdateOnly) {
  5173.     // A preference setting can disable updating for this item
  5174.     try {
  5175.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, aItem.id))) {
  5176.         var status = nsIAddonUpdateCheckListener.STATUS_DISABLED;
  5177.         this._updater.checkForDone(aItem, status);
  5178.         return;
  5179.       }
  5180.     }
  5181.     catch (e) { }
  5182.  
  5183.     // Items managed by the app are not checked for updates.
  5184.     var emDS = this._updater._emDS;
  5185.     if (emDS.getItemProperty(aItem.id, "appManaged") == "true") {
  5186.       var status = nsIAddonUpdateCheckListener.STATUS_APP_MANAGED;
  5187.       this._updater.checkForDone(aItem, status);
  5188.       return;
  5189.     }
  5190.  
  5191.     // Items that have a pending install, uninstall, or upgrade are not checked
  5192.     // for updates.
  5193.     var opType = emDS.getItemProperty(aItem.id, "opType");
  5194.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  5195.         opType == OP_NEEDS_UPGRADE) {
  5196.       var status = nsIAddonUpdateCheckListener.STATUS_PENDING_OP;
  5197.       this._updater.checkForDone(aItem, status);
  5198.       return;
  5199.     }
  5200.  
  5201.     var installLocation = InstallLocations.get(emDS.getInstallLocationKey(aItem.id));
  5202.     // Don't check items for updates that are installed in a location that is
  5203.     // not managed by the app.
  5204.     if (installLocation && (installLocation.name == "winreg-app-global" ||
  5205.         installLocation.name == "winreg-app-user")) {
  5206.       var status = nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED;
  5207.       this._updater.checkForDone(aItem, status);
  5208.       return;
  5209.     }
  5210.  
  5211.     // Don't check items for updates if the location can't be written to except
  5212.     // when performing a version only update.
  5213.     if (!aVersionUpdateOnly && (!installLocation || !installLocation.canAccess)) {
  5214.       var status = nsIAddonUpdateCheckListener.STATUS_READ_ONLY;
  5215.       this._updater.checkForDone(aItem, status);
  5216.       return;
  5217.     }
  5218.  
  5219.     this._versionUpdateOnly = aVersionUpdateOnly;
  5220.     this._item = aItem;
  5221.   
  5222.     // Look for a custom update URI: 1) supplied by a pref, 2) supplied by the
  5223.     // install manifest, 3) the default configuration
  5224.     try {
  5225.       var dsURI = gPref.getComplexValue(PREF_EM_ITEM_UPDATE_URL.replace(/%UUID%/, aItem.id),
  5226.                                         Components.interfaces.nsIPrefLocalizedString).data;
  5227.     }
  5228.     catch (e) { }
  5229.     if (!dsURI)
  5230.       dsURI = aItem.updateRDF;
  5231.     if (!dsURI) {
  5232.       dsURI = gPref.getComplexValue(PREF_UPDATE_DEFAULT_URL,
  5233.                                     Components.interfaces.nsIPrefLocalizedString).data;
  5234.     }
  5235.     dsURI = dsURI.replace(/%ITEM_ID%/g, aItem.id);
  5236.     dsURI = dsURI.replace(/%ITEM_VERSION%/g, aItem.version);
  5237.     dsURI = dsURI.replace(/%ITEM_MAXAPPVERSION%/g, aItem.maxAppVersion);
  5238.     dsURI = dsURI.replace(/%APP_ID%/g, this._updater._appID);
  5239.     dsURI = dsURI.replace(/%APP_VERSION%/g, this._updater._appVersion);
  5240.     dsURI = dsURI.replace(/%REQ_VERSION%/g, 1);
  5241.     dsURI = dsURI.replace(/%APP_OS%/g, gOSTarget);
  5242.     dsURI = dsURI.replace(/%APP_ABI%/g, gXPCOMABI);
  5243.     
  5244.     // escape() does not properly encode + symbols in any embedded FVF strings.
  5245.     dsURI = dsURI.replace(/\+/g, "%2B");
  5246.  
  5247.     // Verify that the URI provided is valid
  5248.     try {
  5249.       var uri = newURI(dsURI);
  5250.     }
  5251.     catch (e) {
  5252.       LOG("RDFItemUpdater:checkForUpdates: There was an error loading the \r\n" + 
  5253.           " update datasource for: " + dsURI + ", item = " + aItem.id + ", error: " + e);
  5254.       this._updater.checkForDone(aItem, 
  5255.                                  nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5256.       return;
  5257.     }
  5258.  
  5259.     LOG("RDFItemUpdater:checkForUpdates sending a request to server for: " + 
  5260.         uri.spec + ", item = " + aItem.objectSource);        
  5261.  
  5262.     var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
  5263.                             .createInstance(Components.interfaces.nsIXMLHttpRequest);
  5264.     request.open("GET", uri.spec, true);
  5265.     request.channel.notificationCallbacks = new BadCertHandler();
  5266.     request.overrideMimeType("text/xml");
  5267.     request.setRequestHeader("Cache-Control", "no-cache");
  5268.  
  5269.     var self = this;
  5270.     request.onerror     = function(event) { self.onXMLError(event, aItem);    };
  5271.     request.onload      = function(event) { self.onXMLLoad(event, aItem);     };
  5272.     request.send(null);
  5273.   },
  5274.  
  5275.   onXMLLoad: function(aEvent, aItem) {
  5276.     var request = aEvent.target;
  5277.     try {
  5278.       checkCert(request.channel);
  5279.     }
  5280.     catch (e) {
  5281.       // This may be overly restrictive in two cases: corporate installations
  5282.       // with a corporate update server using an in-house CA cert (installed
  5283.       // but not "built-in") and lone developers hosting their updates on a
  5284.       // site with a self-signed cert (permanently accepted, otherwise the
  5285.       // BadCertHandler would prevent getting this far). Update checks will
  5286.       // fail in both these scenarios.
  5287.       // How else can we protect the vast majority of updates served from AMO
  5288.       // from the spoofing attack described in bug 340198 while allowing those
  5289.       // other cases? A "hackme" pref? Domain-control certs are cheap, getting
  5290.       // one should not be a barrier in either case.
  5291.       LOG("RDFItemUpdater::onXMLLoad: " + e);
  5292.       this._updater.checkForDone(aItem,
  5293.                                  nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5294.       return;
  5295.     }
  5296.     var responseXML = request.responseXML;
  5297.     if (responseXML)
  5298.       var parseError = (responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR);
  5299.  
  5300.     // If AMO does not return responseXML it is not treated as a failure since
  5301.     // items without an updateURL are checked on AMO. If there is an XML parse
  5302.     // error, responseXML is null, status does NOT equal 200 or 0 (e.g. 200 is
  5303.     // HTTP OK and 0 is returned for a local file) then we don't have valid data.
  5304.     if (!responseXML || parseError || (request.status != 200 && request.status != 0)) {
  5305.       // If the item does not have an updateRDF then the error is from UMO.
  5306.       if (!aItem.updateRDF) {
  5307.         this._updater.checkForDone(aItem, 
  5308.                                    nsIAddonUpdateCheckListener.STATUS_NONE);
  5309.       }
  5310.       else {
  5311.         this._updater.checkForDone(aItem, 
  5312.                                    nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5313.       }
  5314.  
  5315.       return;
  5316.     }
  5317.  
  5318.     var rdfParser = Components.classes["@mozilla.org/rdf/xml-parser;1"]
  5319.                               .createInstance(Components.interfaces.nsIRDFXMLParser)
  5320.     var ds = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
  5321.                        .createInstance(Components.interfaces.nsIRDFDataSource);
  5322.     rdfParser.parseString(ds, request.channel.URI, request.responseText);
  5323.  
  5324.     this.onDatasourceLoaded(ds, aItem);
  5325.   },
  5326.  
  5327.   /**
  5328.    *
  5329.    */
  5330.   onXMLError: function(aEvent, aItem) {
  5331.     try {
  5332.       var request = aEvent.target;
  5333.       // the following may throw (e.g. a local file or timeout)
  5334.       var status = request.status;
  5335.     }
  5336.     catch (e) {
  5337.       request = aEvent.target.channel.QueryInterface(Components.interfaces.nsIRequest);
  5338.       status = request.status;
  5339.     }
  5340.  
  5341.     var statusText = request.statusText;
  5342.  
  5343.     // When status is 0 we don't have a valid channel.
  5344.     if (status == 0)
  5345.       statusText = "nsIXMLHttpRequest channel unavailable";
  5346.  
  5347.     LOG("RDFItemUpdater:onError: There was an error loading the \r\n" + 
  5348.         "the update datasource for item " + aItem.id + ", error: " + statusText);
  5349.     this._updater.checkForDone(aItem, 
  5350.                                nsIAddonUpdateCheckListener.STATUS_FAILURE);
  5351.   },
  5352.  
  5353.   onDatasourceLoaded: function(aDatasource, aLocalItem) {
  5354.     ///////////////////////////////////////////////////////////////////////////    
  5355.     // The extension update RDF file looks something like this:
  5356.     //
  5357.     //  <RDF:Description about="urn:mozilla:extension:{GUID}">
  5358.     //    <em:updates>
  5359.     //      <RDF:Seq>
  5360.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:4.9"/>
  5361.     //        <RDF:li resource="urn:mozilla:extension:{GUID}:5.0"/>
  5362.     //      </RDF:Seq>
  5363.     //    </em:updates>
  5364.     //    <!-- the version of the extension being offered -->
  5365.     //    <em:version>5.0</em:version>
  5366.     //    <em:updateLink>http://www.mysite.com/myext-50.xpi</em:updateLink>
  5367.     //  </RDF:Description>
  5368.     //
  5369.     //  <RDF:Description about="urn:mozilla:extension:{GUID}:4.9">
  5370.     //    <em:version>4.9</em:version>
  5371.     //    <em:targetApplication>
  5372.     //      <RDF:Description>
  5373.     //        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
  5374.     //        <em:minVersion>0.9</em:minVersion>
  5375.     //        <em:maxVersion>1.0</em:maxVersion>
  5376.     //        <em:updateLink>http://www.mysite.com/myext-49.xpi</em:updateLink>
  5377.     //      </RDF:Description>
  5378.     //    </em:targetApplication>
  5379.     //  </RDF:Description>  
  5380.     //
  5381.     // If we get here because the following happened:
  5382.     // 1) User was using Firefox 0.9 with ExtensionX 0.5 (minVersion 0.8, 
  5383.     //    maxVersion 0.9 for Firefox)
  5384.     // 2) User upgraded Firefox to 1.0
  5385.     // 3) |checkForMismatches| deems ExtensionX 0.5 incompatible with this
  5386.     //    new version of Firefox on the basis of its maxVersion
  5387.     // 4) ** We reach this point **
  5388.     //
  5389.     // If the version of ExtensionX (0.5) matches that provided by the 
  5390.     // server, then this is a cue that the author updated the rdf file
  5391.     // or central repository to say "0.5 is ALSO compatible with Firefox 1.0,
  5392.     // no changes are necessary." In this event, the local metadata for
  5393.     // installed ExtensionX (0.5) is freshened with the new maxVersion, 
  5394.     // and we advance to the next item WITHOUT any download/install 
  5395.     // updates.
  5396.     if (!aDatasource.GetAllResources().hasMoreElements()) {
  5397.       LOG("RDFItemUpdater:onDatasourceLoaded: Datasource empty.\r\n" + 
  5398.           "If you are an Extension developer and were expecting there to be\r\n" + 
  5399.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  5400.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  5401.           "\r\nTry checking that: \r\n" + 
  5402.           " 1. Your remote RDF file exists at the location.\r\n" + 
  5403.           " 2. Your RDF file is valid XML (starts with <?xml version=\"1.0?\">\r\n" + 
  5404.           "    and loads in Firefox displaying pretty printed like other XML documents\r\n" + 
  5405.           " 3. Your server is sending the data in the correct MIME\r\n" + 
  5406.           "    type (text/xml)");
  5407.     }      
  5408.     
  5409.   
  5410.     // Parse the response RDF
  5411.     function UpdateData() {}; 
  5412.     UpdateData.prototype = { version: "0.0", updateLink: null, updateHash: null,
  5413.                              minVersion: "0.0", maxVersion: "0.0" };
  5414.     
  5415.     var versionUpdate = new UpdateData();
  5416.     var newestUpdate  = new UpdateData();
  5417.  
  5418.     var newerItem, sameItem;
  5419.     
  5420.     // Firefox 1.0PR+ update.rdf format
  5421.     if (!this._versionUpdateOnly) {
  5422.       // Look for newer versions of this item, we only do this in "normal" 
  5423.       // mode... see comment by ExtensionItemUpdater_checkForUpdates 
  5424.       // about how we do this in all cases but Install Phone Home - which 
  5425.       // only needs to do a version check.
  5426.       this._parseV20UpdateInfo(aDatasource, aLocalItem, newestUpdate, false);
  5427.       if (!newestUpdate.updateLink) {
  5428.         // Firefox 0.9 update.rdf format - does not contain any metadata
  5429.         // that can be used for version updates, so performed in the "all updates"
  5430.         // mode only. 
  5431.         this._parseV10UpdateInfo(aDatasource, aLocalItem, newestUpdate);
  5432.       }
  5433.  
  5434.       newerItem = makeItem(aLocalItem.id, 
  5435.                            newestUpdate.version, 
  5436.                            aLocalItem.installLocationKey,
  5437.                            newestUpdate.minVersion, 
  5438.                            newestUpdate.maxVersion, 
  5439.                            aLocalItem.name, 
  5440.                            newestUpdate.updateLink,
  5441.                            newestUpdate.updateHash,
  5442.                            "", /* Icon URL */
  5443.                            "", /* RDF Update URL */
  5444.                            aLocalItem.type);
  5445.       if (this._updater._isValidUpdate(aLocalItem, newerItem))
  5446.         ++this._updater._updateCount;
  5447.       else
  5448.         newerItem = null;
  5449.     }
  5450.     
  5451.     // Now look for updated version compatibility metadata for the currently
  5452.     // installed version...
  5453.     this._parseV20UpdateInfo(aDatasource, aLocalItem, versionUpdate, true);
  5454.  
  5455.     var result = gVersionChecker.compare(versionUpdate.version, 
  5456.                                           aLocalItem.version);
  5457.     if (result == 0) {
  5458.       // Local version exactly matches the "Version Update" remote version, 
  5459.       // Apply changes into local datasource.
  5460.       sameItem = makeItem(aLocalItem.id, 
  5461.                           versionUpdate.version, 
  5462.                           aLocalItem.installLocationKey,
  5463.                           versionUpdate.minVersion, 
  5464.                           versionUpdate.maxVersion, 
  5465.                           aLocalItem.name,
  5466.                           "", /* XPI Update URL */
  5467.                           "", /* XPI Update Hash */
  5468.                           "", /* Icon URL */
  5469.                           "", /* RDF Update URL */
  5470.                           aLocalItem.type);
  5471.       if (this._updater._isValidUpdate(aLocalItem, sameItem)) {
  5472.         // Install-time updates are not written to the DS because there is no
  5473.         // entry yet, EM just uses the notifications to ascertain (by hand)
  5474.         // whether or not there is a remote maxVersion tweak that makes the 
  5475.         // item being installed compatible.
  5476.         if (!this._updater._applyVersionUpdates(aLocalItem, sameItem))
  5477.           sameItem = null;
  5478.       }
  5479.       else 
  5480.         sameItem = null;
  5481.     }
  5482.     
  5483.     if (newerItem) {
  5484.       LOG("RDFItemUpdater:onDatasourceLoaded: Found a newer version of this item:\r\n" + 
  5485.           newerItem.objectSource);
  5486.     }
  5487.     if (sameItem) {
  5488.       LOG("RDFItemUpdater:onDatasourceLoaded: Found info about the installed\r\n" + 
  5489.           "version of this item: " + sameItem.objectSource);
  5490.     }
  5491.     var item = null, status = nsIAddonUpdateCheckListener.STATUS_NONE;
  5492.     if (!this._versionUpdateOnly && newerItem) {
  5493.       item = newerItem;
  5494.       status = nsIAddonUpdateCheckListener.STATUS_UPDATE;
  5495.     }
  5496.     else if (sameItem) {
  5497.       item = sameItem;
  5498.       status = nsIAddonUpdateCheckListener.STATUS_VERSIONINFO;
  5499.     }
  5500.     else {
  5501.       item = aLocalItem;
  5502.       status = nsIAddonUpdateCheckListener.STATUS_NO_UPDATE;
  5503.     }
  5504.     // Only one call of this._updater.checkForDone is needed for RDF 
  5505.     // responses, since there is only one response per item.
  5506.     this._updater.checkForDone(item, status);
  5507.   },
  5508.  
  5509.   // Parses Firefox 0.9 update.rdf format  
  5510.   _parseV10UpdateInfo: function(aDataSource, aLocalItem, aUpdateData) {
  5511.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  5512.     
  5513.     aUpdateData.version     = this._getPropertyFromResource(aDataSource, extensionRes, 
  5514.                                                             "version", aLocalItem);
  5515.     aUpdateData.updateLink  = this._getPropertyFromResource(aDataSource, extensionRes, 
  5516.                                                             "updateLink", aLocalItem);
  5517.   },
  5518.   
  5519.   // Get a compulsory property from a resource. Reports an error if the 
  5520.   // property was not present. 
  5521.   _getPropertyFromResource: function(aDataSource, aSourceResource, aProperty, aLocalItem) {
  5522.     var rv;
  5523.     try {
  5524.       var property = gRDF.GetResource(EM_NS(aProperty));
  5525.       rv = stringData(aDataSource.GetTarget(aSourceResource, property, true));
  5526.       if (rv === undefined)
  5527.         throw Components.results.NS_ERROR_FAILURE;
  5528.     }
  5529.     catch (e) {
  5530.       // XXXben show console message "aProperty" not found on aSourceResource. 
  5531.       return null;
  5532.     }
  5533.     return rv;
  5534.   },
  5535.   
  5536.   // Parses Firefox 1.0RC1+ update.rdf format
  5537.   _parseV20UpdateInfo: function(aDataSource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  5538.     var extensionRes  = gRDF.GetResource(getItemPrefix(aLocalItem.type) + aLocalItem.id);
  5539.  
  5540.     var updatesArc = gRDF.GetResource(EM_NS("updates"));
  5541.     var updates = aDataSource.GetTarget(extensionRes, updatesArc, true);
  5542.     
  5543.     try {
  5544.       updates = updates.QueryInterface(Components.interfaces.nsIRDFResource);
  5545.     }
  5546.     catch (e) { 
  5547.       LOG("RDFItemUpdater:_parseV20UpdateInfo: No updates were found for:\r\n" + 
  5548.           aLocalItem.id + "\r\n" + 
  5549.           "If you are an Extension developer and were expecting there to be\r\n" + 
  5550.           "updates, this could mean any number of things, since the RDF system\r\n" + 
  5551.           "doesn't give up much in the way of information when the load fails.\r\n" + 
  5552.           "\r\nTry checking that: \r\n" + 
  5553.           " 1. Your RDF File is correct - e.g. check that there is a top level\r\n" + 
  5554.           "    RDF Resource with a URI urn:mozilla:extension:{GUID}, and that\r\n" + 
  5555.           "    the <em:updates> listed all have matching GUIDs.");
  5556.       return; 
  5557.     }
  5558.     
  5559.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  5560.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  5561.     if (cu.IsContainer(aDataSource, updates)) {
  5562.       var ctr = getContainer(aDataSource, updates);
  5563.  
  5564.       // In "all update types" mode, we look for newer versions, starting with the 
  5565.       // current installed version.
  5566.       if (!aVersionUpdatesOnly) 
  5567.         aUpdateData.version = aLocalItem.version;
  5568.  
  5569.       var versions = ctr.GetElements();
  5570.       while (versions.hasMoreElements()) {
  5571.         // There are two different methodologies for collecting version 
  5572.         // information depending on whether or not we've bene invoked in 
  5573.         // "version updates only" mode or "version+newest" mode. 
  5574.         var version = versions.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5575.         this._parseV20Update(aDataSource, version, aLocalItem, aUpdateData, aVersionUpdatesOnly);
  5576.         if (aVersionUpdatesOnly && aUpdateData.updateLink)
  5577.           break;
  5578.       }
  5579.     }
  5580.   },
  5581.   
  5582.   _parseV20Update: function(aDataSource, aUpdateResource, aLocalItem, aUpdateData, aVersionUpdatesOnly) {
  5583.     var version = this._getPropertyFromResource(aDataSource, aUpdateResource, 
  5584.                                                 "version", aLocalItem);
  5585.     var taArc = gRDF.GetResource(EM_NS("targetApplication"));
  5586.     var targetApps = aDataSource.GetTargets(aUpdateResource, taArc, true);
  5587.     while (targetApps.hasMoreElements()) {
  5588.       var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5589.       var id = this._getPropertyFromResource(aDataSource, targetApp, "id", aLocalItem);
  5590.       if (id != this._updater._appID)
  5591.         continue;
  5592.       
  5593.       var result = gVersionChecker.compare(version, aLocalItem.version);
  5594.       if (aVersionUpdatesOnly ? result == 0 : result > 0) {
  5595.         aUpdateData.version = version;
  5596.         aUpdateData.updateLink = this._getPropertyFromResource(aDataSource, targetApp, "updateLink", aLocalItem);
  5597.         aUpdateData.updateHash = this._getPropertyFromResource(aDataSource, targetApp, "updateHash", aLocalItem);
  5598.         aUpdateData.minVersion = this._getPropertyFromResource(aDataSource, targetApp, "minVersion", aLocalItem);
  5599.         aUpdateData.maxVersion = this._getPropertyFromResource(aDataSource, targetApp, "maxVersion", aLocalItem);
  5600.       }
  5601.     }
  5602.   }
  5603. };
  5604.  
  5605. /**
  5606.  * A Datasource that holds Extensions. 
  5607.  * - Implements nsIRDFDataSource to drive UI
  5608.  * - Uses a RDF/XML datasource for storage (this is undesirable)
  5609.  * 
  5610.  * @constructor
  5611.  */
  5612. function ExtensionsDataSource(em) {
  5613.   this._em = em;
  5614.   
  5615.   this._itemRoot = gRDF.GetResource(RDFURI_ITEM_ROOT);
  5616.   this._defaultTheme = gRDF.GetResource(RDFURI_DEFAULT_THEME);
  5617.   gRDF.RegisterDataSource(this, true);
  5618. }
  5619. ExtensionsDataSource.prototype = {
  5620.   _inner    : null,
  5621.   _em       : null,
  5622.   _itemRoot     : null,
  5623.   _defaultTheme : null,
  5624.   
  5625.   /**
  5626.    * Determine if an item is compatible
  5627.    * @param   datasource
  5628.    *          The datasource to inspect for compatibility - can be the main
  5629.    *          datasource or an Install Manifest.
  5630.    * @param   source
  5631.    *          The RDF Resource of the item to inspect for compatibility.
  5632.    * @param   version
  5633.    *          The version of the application we are checking for compatibility
  5634.    *          against. If this parameter is undefined, the version of the running
  5635.    *          application is used.
  5636.    * @returns true if the item is compatible with this version of the 
  5637.    *          application, false, otherwise.
  5638.    */
  5639.   isCompatible: function (datasource, source, version) {
  5640.     // The Default Theme is always compatible. 
  5641.     if (source.EqualsNode(this._defaultTheme))
  5642.       return true;
  5643.  
  5644.     if (version === undefined) {
  5645.       version = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  5646.                         gApp.version);
  5647.     }              
  5648.     var appID = gApp.ID;
  5649.     
  5650.     var targets = datasource.GetTargets(source, EM_R("targetApplication"), true);
  5651.     var idRes = EM_R("id");
  5652.     var minVersionRes = EM_R("minVersion");
  5653.     var maxVersionRes = EM_R("maxVersion");
  5654.     while (targets.hasMoreElements()) {
  5655.       var targetApp = targets.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5656.       var id          = stringData(datasource.GetTarget(targetApp, idRes, true));
  5657.       var minVersion  = stringData(datasource.GetTarget(targetApp, minVersionRes, true));
  5658.       var maxVersion  = stringData(datasource.GetTarget(targetApp, maxVersionRes, true));
  5659.       if (id == appID) {
  5660.         var versionChecker = getVersionChecker();
  5661.         return ((versionChecker.compare(version, minVersion) >= 0) &&
  5662.                 (versionChecker.compare(version, maxVersion) <= 0));
  5663.       }
  5664.     }
  5665.     return false;
  5666.   },
  5667.   
  5668.   /**
  5669.    * Gets a list of items that are incompatible with a specific application version.
  5670.    * @param   appID
  5671.    *          The ID of the application - XXXben unused?
  5672.    * @param   appVersion
  5673.    *          The Version of the application to check for incompatibility against.
  5674.    * @param   desiredType
  5675.    *          The nsIUpdateItem type of items to look for
  5676.    * @param   includeDisabled
  5677.    *          Whether or not disabled items should be included in the set returned
  5678.    * @returns An array of nsIUpdateItems that are incompatible with the application
  5679.    *          ID/Version supplied.
  5680.    */
  5681.   getIncompatibleItemList: function(appID, appVersion, desiredType, includeDisabled) {
  5682.     var items = [];
  5683.     var ctr = getContainer(this._inner, this._itemRoot);
  5684.     var elements = ctr.GetElements();
  5685.     while (elements.hasMoreElements()) {
  5686.       var item = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5687.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  5688.       var type = this.getItemProperty(id, "type");
  5689.       // Skip this item if we're not seeking disabled items
  5690.       if (!includeDisabled &&
  5691.          (this.getItemProperty(id, "disabled") == "true" ||
  5692.           this.getItemProperty(id, "appDisabled") == "true"))
  5693.         continue;
  5694.       
  5695.       // If the id of this item matches one of the items potentially installed
  5696.       // with and maintained by this application AND it is installed in the 
  5697.       // global install location (i.e. the place installed by the app installer)
  5698.       // it is and can be managed by the update file - it's not an item that has
  5699.       // been manually installed by the user into their profile dir, and as such
  5700.       // it is always compatible with the next release of the application since
  5701.       // we will continue to support it.
  5702.       var locationKey = this.getItemProperty(id, "installLocation");
  5703.       var appManaged = this.getItemProperty(id, "appManaged") == "true";
  5704.       if (appManaged && locationKey == KEY_APP_GLOBAL)
  5705.         continue;
  5706.  
  5707.       if (type != -1 && (type & desiredType) && 
  5708.           !this.isCompatible(this, item, appVersion))
  5709.         items.push(this.getItemForID(id));
  5710.     }
  5711.     return items;
  5712.   },
  5713.   
  5714.   /**
  5715.    * Gets a list of items of a specific type
  5716.    * @param   desiredType
  5717.    *          The nsIUpdateItem type of items to return
  5718.    * @param   countRef
  5719.    *          The XPCJS reference to the size of the returned array
  5720.    * @returns An array of nsIUpdateItems, populated only with an item for |id|
  5721.    *          if |id| is non-null, otherwise all items matching the specified
  5722.    *          type.
  5723.    */
  5724.   getItemList: function(desiredType, countRef) {
  5725.     var items = [];
  5726.     var ctr = getContainer(this, this._itemRoot);      
  5727.     var elements = ctr.GetElements();
  5728.     while (elements.hasMoreElements()) {
  5729.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5730.       var eID = stripPrefix(e.Value, PREFIX_ITEM_URI);
  5731.       var type = this.getItemProperty(eID, "type");
  5732.       if (type != -1 && type & desiredType)
  5733.         items.push(this.getItemForID(eID));
  5734.     }
  5735.     countRef.value = items.length;
  5736.     return items;
  5737.   },
  5738.  
  5739.   /**
  5740.    * Get a list of Item IDs that have a flag set
  5741.    * @param   flag
  5742.    *          The name of an RDF property (less EM_NS) to check for
  5743.    * @param   desiredType
  5744.    *          The nsIUpdateItem type of item to look for
  5745.    * @returns An array of Item IDs 
  5746.    *
  5747.    * XXXben - this function is a little weird since it returns an array of 
  5748.    *          strings, not an array of nsIUpdateItems...  
  5749.    */
  5750.   getItemsWithFlagUnset: function(flag, desiredType) {
  5751.     var items = [];
  5752.  
  5753.     var ctr = getContainer(this, this._itemRoot);    
  5754.     var elements = ctr.GetElements();
  5755.     while (elements.hasMoreElements()) {
  5756.       var e = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  5757.       var id = stripPrefix(e.Value, PREFIX_ITEM_URI);
  5758.       var type = this.getItemProperty(id, "type");
  5759.       if (type != -1 && type & desiredType) {
  5760.         var value = this.GetTarget(e, EM_R(flag), true);
  5761.         if (!value)
  5762.           items.push(id);
  5763.       }
  5764.     }
  5765.     return items;
  5766.   },
  5767.   
  5768.   /**
  5769.    * Constructs an nsIUpdateItem for the given item ID
  5770.    * @param   id
  5771.    *          The GUID of the item to construct a nsIUpdateItem for
  5772.    * @returns The nsIUpdateItem for the id.
  5773.    */  
  5774.   getItemForID: function(id) {
  5775.     var r = getResourceForID(id);
  5776.     if (!r)
  5777.       return null;
  5778.     
  5779.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  5780.     var updateHash = this.getItemProperty(id, "availableUpdateHash");
  5781.     return makeItem(id, 
  5782.                     this.getItemProperty(id, "version"), 
  5783.                     this.getItemProperty(id, "installLocation"),
  5784.                     targetAppInfo ? targetAppInfo.minVersion : "",
  5785.                     targetAppInfo ? targetAppInfo.maxVersion : "",
  5786.                     this.getItemProperty(id, "name"),
  5787.                     this.getItemProperty(id, "availableUpdateURL"),
  5788.                     updateHash ? updateHash : "",
  5789.                     this.getItemProperty(id, "iconURL"), 
  5790.                     this.getItemProperty(id, "updateURL"), 
  5791.                     this.getItemProperty(id, "type"));
  5792.   },
  5793.   
  5794.   /**
  5795.    * Gets the name of the Install Location where an item is installed.
  5796.    * @param   id
  5797.    *          The GUID of the item to locate an Install Location for
  5798.    * @returns The string name of the Install Location where the item is 
  5799.    *          installed.
  5800.    */
  5801.   getInstallLocationKey: function(id) {
  5802.     return this.getItemProperty(id, "installLocation");
  5803.   },
  5804.   
  5805.   /**
  5806.    * Sets an RDF property on an item in a datasource. Does not create
  5807.    * multiple assertions
  5808.    * @param   datasource
  5809.    *          The target datasource where the property should be set
  5810.    * @param   source
  5811.    *          The RDF Resource to set the property on
  5812.    * @param   property
  5813.    *          The RDF Resource of the property to set
  5814.    * @param   newValue
  5815.    *          The RDF Node containing the new property value
  5816.    */
  5817.   _setProperty: function(datasource, source, property, newValue) {
  5818.     var oldValue = datasource.GetTarget(source, property, true);
  5819.     if (oldValue) {
  5820.       if (newValue)
  5821.         datasource.Change(source, property, oldValue, newValue);
  5822.       else
  5823.         datasource.Unassert(source, property, oldValue);
  5824.     }
  5825.     else if (newValue)
  5826.       datasource.Assert(source, property, newValue, true);
  5827.   },
  5828.   
  5829.   /**
  5830.    * Sets the target application info for an item in the Extensions
  5831.    * datasource and in the item's install manifest if it is installed in a
  5832.    * profile's extensions directory, it exists, and we have write access.
  5833.    * @param   id
  5834.    *          The ID of the item to update target application info for
  5835.    * @param   minVersion
  5836.    *          The minimum version of the target application that this item can
  5837.    *          run in
  5838.    * @param   maxVersion
  5839.    *          The maximum version of the target application that this item can
  5840.    *          run in
  5841.    */
  5842.   updateTargetAppInfo: function(id, minVersion, maxVersion)
  5843.   {
  5844.     // Update the Extensions datasource
  5845.     this.setTargetApplicationInfo(id, minVersion, maxVersion, null);
  5846.  
  5847.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  5848.     if (installLocation.name != KEY_APP_PROFILE)
  5849.       return;
  5850.  
  5851.     var installManifestFile = installLocation.getItemFile(id, FILE_INSTALL_MANIFEST);
  5852.     // Only update if the item exists and we can write to the location
  5853.     if (installManifestFile.exists() && installLocation.canAccess)
  5854.       this.setTargetApplicationInfo(id, minVersion, maxVersion,
  5855.                                     getInstallManifest(installManifestFile));
  5856.   },
  5857.  
  5858.   /**
  5859.    * Gets the updated target application info if it exists for an item from
  5860.    * the Extensions datasource during an installation or upgrade.
  5861.    * @param   id
  5862.    *          The ID of the item to discover updated target application info for
  5863.    * @returns A JS Object with the following properties:
  5864.    *          "id"            The id of the item
  5865.    *          "minVersion"    The updated minimum version of the target
  5866.    *                          application that this item can run in
  5867.    *          "maxVersion"    The updated maximum version of the target
  5868.    *                          application that this item can run in
  5869.    */
  5870.   getUpdatedTargetAppInfo: function(id) {
  5871.     // The default theme is always compatible so there is never update info.
  5872.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  5873.       return null;
  5874.  
  5875.     var appID = gApp.ID;
  5876.     var r = getResourceForID(id);
  5877.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  5878.     if (!targetApps.hasMoreElements())
  5879.       targetApps = this._inner.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  5880.     while (targetApps.hasMoreElements()) {
  5881.       var targetApp = targetApps.getNext();
  5882.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5883.         try {
  5884.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  5885.           if (foundAppID != appID) // Different target application
  5886.             continue;
  5887.           var updatedMinVersion = this._inner.GetTarget(targetApp, EM_R("updatedMinVersion"), true);
  5888.           var updatedMaxVersion = this._inner.GetTarget(targetApp, EM_R("updatedMaxVersion"), true);
  5889.           if (updatedMinVersion && updatedMaxVersion)
  5890.             return { id        : id,
  5891.                      minVersion: stringData(updatedMinVersion),
  5892.                      maxVersion: stringData(updatedMaxVersion) };
  5893.           else
  5894.             return null;
  5895.         }
  5896.         catch (e) { 
  5897.           continue;
  5898.         }
  5899.       }
  5900.     }
  5901.     return null;
  5902.   },
  5903.   
  5904.   /**
  5905.    * Sets the updated target application info for an item in the Extensions
  5906.    * datasource during an installation or upgrade.
  5907.    * @param   id
  5908.    *          The ID of the item to set updated target application info for
  5909.    * @param   updatedMinVersion
  5910.    *          The updated minimum version of the target application that this
  5911.    *          item can run in
  5912.    * @param   updatedMaxVersion
  5913.    *          The updated maximum version of the target application that this
  5914.    *          item can run in
  5915.    */
  5916.   setUpdatedTargetAppInfo: function(id, updatedMinVersion, updatedMaxVersion) {
  5917.     // The default theme is always compatible so it is never updated.
  5918.     if (getResourceForID(id).EqualsNode(this._defaultTheme))
  5919.       return;
  5920.  
  5921.     // Version/Dependency Info
  5922.     var updatedMinVersionRes = EM_R("updatedMinVersion");
  5923.     var updatedMaxVersionRes = EM_R("updatedMaxVersion");
  5924.  
  5925.     var appID = gApp.ID;
  5926.     var r = getResourceForID(id);
  5927.     var targetApps = this._inner.GetTargets(r, EM_R("targetApplication"), true);
  5928.     // add updatedMinVersion and updatedMaxVersion for an install else an upgrade
  5929.     if (!targetApps.hasMoreElements()) {
  5930.       var idRes = EM_R("id");
  5931.       var targetRes = getResourceForID(id);
  5932.       var property = EM_R("targetApplication");
  5933.       var anon = gRDF.GetAnonymousResource();
  5934.       this._inner.Assert(anon, idRes, EM_L(appID), true);
  5935.       this._inner.Assert(anon, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  5936.       this._inner.Assert(anon, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  5937.       this._inner.Assert(targetRes, property, anon, true);
  5938.     }
  5939.     else {
  5940.       while (targetApps.hasMoreElements()) {
  5941.         var targetApp = targetApps.getNext();
  5942.         if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5943.           var foundAppID = stringData(this._inner.GetTarget(targetApp, EM_R("id"), true));
  5944.           if (foundAppID != appID) // Different target application
  5945.             continue;
  5946.           this._inner.Assert(targetApp, updatedMinVersionRes, EM_L(updatedMinVersion), true);
  5947.           this._inner.Assert(targetApp, updatedMaxVersionRes, EM_L(updatedMaxVersion), true);
  5948.           break;
  5949.         }
  5950.       }
  5951.     }
  5952.     this.Flush();
  5953.   },
  5954.  
  5955.   /**
  5956.    * Gets the target application info for an item from a datasource.
  5957.    * @param   id
  5958.    *          The GUID of the item to discover target application info for
  5959.    * @param   datasource
  5960.    *          The datasource to look up target application info in
  5961.    * @returns A JS Object with the following properties:
  5962.    *          "minVersion"    The minimum version of the target application
  5963.    *                          that this item can run in
  5964.    *          "maxVersion"    The maximum version of the target application
  5965.    *                          that this item can run in
  5966.    *          or null, if no target application data exists for the specified
  5967.    *          id in the supplied datasource.
  5968.    */
  5969.   getTargetApplicationInfo: function(id, datasource) {
  5970.     // The default theme is always compatible. 
  5971.     if (getResourceForID(id).EqualsNode(this._defaultTheme)) {
  5972.       var ver = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  5973.                         gApp.version);
  5974.       return { minVersion: ver, maxVersion: ver };
  5975.     }
  5976.     var appID = gApp.ID;
  5977.     var r = getResourceForID(id);
  5978.     var targetApps = datasource.GetTargets(r, EM_R("targetApplication"), true);
  5979.     if (!targetApps)
  5980.       return null;
  5981.     if (!targetApps.hasMoreElements())
  5982.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  5983.     while (targetApps.hasMoreElements()) {
  5984.       var targetApp = targetApps.getNext();
  5985.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  5986.         try {
  5987.           var foundAppID = stringData(datasource.GetTarget(targetApp, EM_R("id"), true));
  5988.           if (foundAppID != appID) // Different target application
  5989.             continue;
  5990.           
  5991.           return { minVersion: stringData(datasource.GetTarget(targetApp, EM_R("minVersion"), true)),
  5992.                    maxVersion: stringData(datasource.GetTarget(targetApp, EM_R("maxVersion"), true)) };
  5993.         }
  5994.         catch (e) { 
  5995.           continue;
  5996.         }
  5997.       }
  5998.     }
  5999.     return null;
  6000.   },
  6001.   
  6002.   /**
  6003.    * Sets the target application info for an item in a datasource.
  6004.    * @param   id
  6005.    *          The GUID of the item to discover target application info for
  6006.    * @param   minVersion
  6007.    *          The minimum version of the target application that this item can
  6008.    *          run in
  6009.    * @param   maxVersion
  6010.    *          The maximum version of the target application that this item can
  6011.    *          run in
  6012.    * @param   datasource
  6013.    *          The datasource to loko up target application info in
  6014.    */
  6015.   setTargetApplicationInfo: function(id, minVersion, maxVersion, datasource) {
  6016.     var targetDataSource = datasource;
  6017.     if (!targetDataSource)
  6018.       targetDataSource = this._inner;
  6019.       
  6020.     var appID = gApp.ID;
  6021.     var r = getResourceForID(id);
  6022.     var targetApps = targetDataSource.GetTargets(r, EM_R("targetApplication"), true);
  6023.     if (!targetApps.hasMoreElements())
  6024.       targetApps = datasource.GetTargets(gInstallManifestRoot, EM_R("targetApplication"), true); 
  6025.     while (targetApps.hasMoreElements()) {
  6026.       var targetApp = targetApps.getNext();
  6027.       if (targetApp instanceof Components.interfaces.nsIRDFResource) {
  6028.         var foundAppID = stringData(targetDataSource.GetTarget(targetApp, EM_R("id"), true));
  6029.         if (foundAppID != appID) // Different target application
  6030.           continue;
  6031.         
  6032.         this._setProperty(targetDataSource, targetApp, EM_R("minVersion"), EM_L(minVersion));
  6033.         this._setProperty(targetDataSource, targetApp, EM_R("maxVersion"), EM_L(maxVersion));
  6034.         
  6035.         // If we were setting these properties on the main datasource, flush
  6036.         // it now. (Don't flush changes set on Install Manifests - they are
  6037.         // fleeting).
  6038.         if (!datasource)
  6039.           this.Flush();
  6040.  
  6041.         break;
  6042.       }
  6043.     }
  6044.   },
  6045.   
  6046.   /** 
  6047.    * Gets a property of an item
  6048.    * @param   id
  6049.    *          The GUID of the item
  6050.    * @param   property
  6051.    *          The name of the property (excluding EM_NS)
  6052.    * @returns The literal value of the property, or undefined if there is no 
  6053.    *          value.
  6054.    */
  6055.   getItemProperty: function(id, property) { 
  6056.     var item = getResourceForID(id);
  6057.     if (!item) {
  6058.       LOG("getItemProperty failing for lack of an item. This means getResourceForItem \
  6059.            failed to locate a resource for aItemID (item ID = " + id + ", property = " + property + ")");
  6060.     }
  6061.     else 
  6062.       return this._getItemProperty(item, property);
  6063.     return undefined;
  6064.   },
  6065.   
  6066.   /**
  6067.    * Gets a property of an item resource
  6068.    * @param   itemResource
  6069.    *          The RDF Resource of the item
  6070.    * @param   property
  6071.    *          The name of the property (excluding EM_NS)
  6072.    * @returns The literal value of the property, or undefined if there is no
  6073.    *          value.
  6074.    */
  6075.   _getItemProperty: function(itemResource, property) {
  6076.     var target = this.GetTarget(itemResource, EM_R(property), true);
  6077.     var value = stringData(target);
  6078.     if (value === undefined)
  6079.       value = intData(target);
  6080.     return value === undefined ? "" : value;
  6081.   },
  6082.   
  6083.   /**
  6084.    * Sets a property on an item.
  6085.    * @param   id
  6086.    *          The GUID of the item
  6087.    * @param   propertyArc
  6088.    *          The RDF Resource of the property arc
  6089.    * @param   propertyValue
  6090.    *          A nsIRDFLiteral value of the property to be set
  6091.    */
  6092.   setItemProperty: function (id, propertyArc, propertyValue) {
  6093.     var item = getResourceForID(id);
  6094.     this._setProperty(this._inner, item, propertyArc, propertyValue);
  6095.     this.Flush();  
  6096.   },
  6097.  
  6098.   /**
  6099.    * Inserts the RDF resource for an item into a container.
  6100.    * @param   id
  6101.    *          The GUID of the item
  6102.    */
  6103.   insertItemIntoContainer: function(id) {
  6104.     // Get the target container and resource
  6105.     var ctr = getContainer(this._inner, this._itemRoot);
  6106.     var itemResource = getResourceForID(id);
  6107.     // Don't bother adding the extension to the list if it's already there. 
  6108.     // (i.e. we're upgrading)
  6109.     var oldIndex = ctr.IndexOf(itemResource);
  6110.     if (oldIndex == -1)
  6111.       ctr.AppendElement(itemResource);
  6112.     this.Flush();
  6113.   }, 
  6114.  
  6115.   /**
  6116.    * Removes the RDF resource for an item from its container.
  6117.    * @param   id
  6118.    *          The GUID of the item
  6119.    */
  6120.   removeItemFromContainer: function(id) {
  6121.     var ctr = getContainer(this._inner, this._itemRoot);
  6122.     var itemResource = getResourceForID(id);
  6123.     ctr.RemoveElement(itemResource, true);
  6124.     this.Flush();
  6125.   },
  6126.  
  6127.   /**
  6128.    * Removes a corrupt item entry from the extension list added due to buggy 
  6129.    * code in previous EM versions!  
  6130.    * @param   id
  6131.    *          The GUID of the item
  6132.    */
  6133.   removeCorruptItem: function(id) {
  6134.     this.removeItemMetadata(id);
  6135.     this.removeItemFromContainer(id);
  6136.   },
  6137.  
  6138.   /**
  6139.    * Removes a corrupt download entry from the list
  6140.    * @param   uri
  6141.    *          The RDF URI of the item.
  6142.    * @returns The RDF Resource of the removed entry 
  6143.    */
  6144.   removeCorruptDLItem: function(uri) {
  6145.     var itemResource = gRDF.GetResource(uri);
  6146.     var ctr = getContainer(this._inner, this._itemRoot);
  6147.     if (ctr.IndexOf(itemResource) != -1) {
  6148.       ctr.RemoveElement(itemResource, true);
  6149.       this._cleanResource(itemResource);
  6150.       this.Flush();
  6151.     }
  6152.     return itemResource;
  6153.   },
  6154.   
  6155.   /**
  6156.    * Copies metadata from an Install Manifest Datasource into the Extensions
  6157.    * DataSource.
  6158.    * @param   id
  6159.    *          The GUID of the item
  6160.    * @param   installManifest
  6161.    *          The Install Manifest datasource we are copying from
  6162.    * @param   installLocation
  6163.    *          The Install Location of the item. 
  6164.    */
  6165.   addItemMetadata: function(id, installManifest, installLocation) {
  6166.     // Copy the assertions over from the source datasource. 
  6167.     var targetRes = getResourceForID(id);
  6168.     // Assert properties with single values
  6169.     var singleProps = ["version", "name", "description", "creator", "homepageURL", 
  6170.                        "updateURL", "updateService", "optionsURL", "aboutURL", 
  6171.                        "iconURL", "internalName"];
  6172.  
  6173.     // Items installed into restricted Install Locations can also be locked 
  6174.     // (can't be removed or disabled), and hidden (not shown in the UI)
  6175.     if (installLocation.restricted)
  6176.       singleProps = singleProps.concat(["locked", "hidden"]);
  6177.     if (installLocation.name == KEY_APP_GLOBAL) 
  6178.       singleProps = singleProps.concat(["appManaged"]);
  6179.     for (var i = 0; i < singleProps.length; ++i) {
  6180.       var property = EM_R(singleProps[i]);
  6181.       var literal = installManifest.GetTarget(gInstallManifestRoot, property, true);
  6182.       // If literal is null, _setProperty will remove any existing.
  6183.       this._setProperty(this._inner, targetRes, property, literal);
  6184.     }    
  6185.     
  6186.     // Assert properties with multiple values    
  6187.     var manyProps = ["contributor"];
  6188.     for (var i = 0; i < manyProps.length; ++i) {
  6189.       var property = EM_R(manyProps[i]);
  6190.       var literals = installManifest.GetTargets(gInstallManifestRoot, property, true);
  6191.       
  6192.       var oldValues = this._inner.GetTargets(targetRes, property, true);
  6193.       while (oldValues.hasMoreElements()) {
  6194.         var oldValue = oldValues.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6195.         this._inner.Unassert(targetRes, property, oldValue);
  6196.       }
  6197.       while (literals.hasMoreElements()) {
  6198.         var literal = literals.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6199.         this._inner.Assert(targetRes, property, literal, true);
  6200.       }
  6201.     }
  6202.  
  6203.     // Version/Dependency Info
  6204.     var versionProps = ["targetApplication", "requires"];
  6205.     var idRes = EM_R("id");
  6206.     var minVersionRes = EM_R("minVersion");
  6207.     var maxVersionRes = EM_R("maxVersion");
  6208.     for (var i = 0; i < versionProps.length; ++i) {
  6209.       var property = EM_R(versionProps[i]);
  6210.       var newVersionInfos = installManifest.GetTargets(gInstallManifestRoot, property, true);
  6211.  
  6212.       var oldVersionInfos = this._inner.GetTargets(targetRes, property, true);
  6213.       while (oldVersionInfos.hasMoreElements()) {
  6214.         var oldVersionInfo = oldVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6215.         this._cleanResource(oldVersionInfo);
  6216.         this._inner.Unassert(targetRes, property, oldVersionInfo);
  6217.       }
  6218.       while (newVersionInfos.hasMoreElements()) {
  6219.         var newVersionInfo = newVersionInfos.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6220.         var anon = gRDF.GetAnonymousResource();
  6221.         this._inner.Assert(anon, idRes, installManifest.GetTarget(newVersionInfo, idRes, true), true);
  6222.         this._inner.Assert(anon, minVersionRes, installManifest.GetTarget(newVersionInfo, minVersionRes, true), true);
  6223.         this._inner.Assert(anon, maxVersionRes, installManifest.GetTarget(newVersionInfo, maxVersionRes, true), true);
  6224.         this._inner.Assert(targetRes, property, anon, true);
  6225.       }
  6226.     }
  6227.     this.updateProperty(id, "opType");
  6228.     this.updateProperty(id, "updateable");
  6229.     this.updateProperty(id, "displayDescription");
  6230.     this.Flush();
  6231.   },
  6232.   
  6233.   /**
  6234.    * Strips an item entry of all assertions.
  6235.    * @param   id
  6236.    *          The GUID of the item
  6237.    */
  6238.   removeItemMetadata: function(id) {
  6239.     var item = getResourceForID(id);
  6240.     var resources = ["targetApplication", "requires"];
  6241.     for (var i = 0; i < resources.length; ++i) {
  6242.       var targetApps = this._inner.GetTargets(item, EM_R(resources[i]), true);
  6243.       while (targetApps.hasMoreElements()) {
  6244.         var targetApp = targetApps.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6245.         this._cleanResource(targetApp);
  6246.       }
  6247.     }
  6248.  
  6249.     this._cleanResource(item);
  6250.   },
  6251.   
  6252.   /**
  6253.    * Strips a resource of all outbound assertions. We use methods like this 
  6254.    * since the RDFXMLDatasource will write out all assertions, even if they
  6255.    * are not connected through our root. 
  6256.    * @param   resource
  6257.    *          The resource to clean. 
  6258.    */
  6259.   _cleanResource: function(resource) {
  6260.     // Remove outward arcs
  6261.     var arcs = this._inner.ArcLabelsOut(resource);
  6262.     while (arcs.hasMoreElements()) {
  6263.       var arc = arcs.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6264.       var targets = this._inner.GetTargets(resource, arc, true);
  6265.       while (targets.hasMoreElements()) {
  6266.         var value = targets.getNext().QueryInterface(Components.interfaces.nsIRDFNode);
  6267.         if (value)
  6268.           this._inner.Unassert(resource, arc, value);
  6269.       }
  6270.     }
  6271.   },
  6272.   
  6273.   /**
  6274.    * Notify views that this propery has changed (this is for properties that
  6275.    * are implemented by this datasource rather than by the inner in-memory
  6276.    * datasource and thus do not get free change handling).
  6277.    * @param   id 
  6278.    *          The GUID of the item to update the property for.
  6279.    * @param   property
  6280.    *          The property (less EM_NS) to update.
  6281.    */
  6282.   updateProperty: function(id, property) {
  6283.     var item = getResourceForID(id);
  6284.     var propertyResource = EM_R(property);
  6285.     var value = this.GetTarget(item, propertyResource, true);
  6286.     if (item && value) {
  6287.       for (var i = 0; i < this._observers.length; ++i)
  6288.         this._observers[i].onChange(this, item, propertyResource, 
  6289.                                     EM_L(""), value);
  6290.     }
  6291.   },
  6292.   
  6293.   /**
  6294.    * Move an Item to the index of another item in its container.
  6295.    * @param   movingID
  6296.    *          The ID of the item to be moved.
  6297.    * @param   destinationID
  6298.    *          The ID of an item to move another item to.
  6299.    */
  6300.   moveToIndexOf: function(movingID, destinationID) {
  6301.     var extensions = gRDF.GetResource(RDFURI_ITEM_ROOT);
  6302.     var ctr = getContainer(this._inner, extensions);
  6303.     var item = gRDF.GetResource(movingID);
  6304.     var index = ctr.IndexOf(gRDF.GetResource(destinationID));
  6305.     if (index == -1)
  6306.       index = 1; // move to the beginning if destinationID is not found
  6307.     this._inner.beginUpdateBatch();
  6308.     ctr.RemoveElement(item, true);
  6309.     ctr.InsertElementAt(item, index, true);
  6310.     this._inner.endUpdateBatch();
  6311.     this.Flush();
  6312.   },
  6313.  
  6314.   /**
  6315.    * Determines if an Item is an active download
  6316.    * @param   id
  6317.    *          The GUID of the item
  6318.    * @returns true if the item is an active download, false otherwise.
  6319.    */
  6320.   isDownloadItem: function(id) {
  6321.     return this.getItemProperty(id, "downloadURL") != "";
  6322.   },
  6323.  
  6324.   /**
  6325.    * Adds an entry representing an active download to the appropriate container
  6326.    * @param   addon
  6327.    *          An object implementing nsIUpdateItem for the addon being 
  6328.    *          downloaded.
  6329.    */
  6330.   addDownload: function(addon) {
  6331.     if (addon.id != addon.xpiURL)
  6332.       return;
  6333.     var res = gRDF.GetResource(addon.xpiURL);
  6334.     this._setProperty(this._inner, res, EM_R("name"), EM_L(addon.name));
  6335.     this._setProperty(this._inner, res, EM_R("version"), EM_L(addon.version));
  6336.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(addon.iconURL));
  6337.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(addon.xpiURL));
  6338.     this._setProperty(this._inner, res, EM_R("type"), EM_I(addon.type));
  6339.  
  6340.     var ctr = getContainer(this._inner, this._itemRoot);
  6341.     if (ctr.IndexOf(res) == -1)
  6342.       ctr.AppendElement(res);
  6343.     
  6344.     this.Flush();
  6345.   },
  6346.   
  6347.   /**
  6348.    * Adds an entry representing an item that is incompatible and is being
  6349.    * checked for a compatibility update.
  6350.    * @param   name
  6351.    *          The display name of the item being checked
  6352.    * @param   url
  6353.    *          The URL string of the xpi file that has been staged. This is
  6354.    *          also used for installLocation to make this an independently
  6355.    *          managed item
  6356.    * @param   type
  6357.    *          The nsIUpdateItem type of the item
  6358.    * @param   version
  6359.    *          The version of the item
  6360.    */
  6361.   addIncompatibleUpdateItem: function(name, url, type, version) {
  6362.     // type must be TYPE_EXTENSION for a multi_xpi to display in the manager.
  6363.     if (type == nsIUpdateItem.TYPE_MULTI_XPI)
  6364.       type = nsIUpdateItem.TYPE_EXTENSION;
  6365.  
  6366.     var iconURL = (type == nsIUpdateItem.TYPE_THEME) ? URI_GENERIC_ICON_THEME :
  6367.                                                        URI_GENERIC_ICON_XPINSTALL;
  6368.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6369.     var updateMsg = extensionsStrings.formatStringFromName("incompatibleUpdateMessage",
  6370.                                                            [BundleManager.appName, name], 2)
  6371.  
  6372.     var res = gRDF.GetResource(url);
  6373.     this._setProperty(this._inner, res, EM_R("name"), EM_L(name));
  6374.     this._setProperty(this._inner, res, EM_R("iconURL"), EM_L(iconURL));
  6375.     this._setProperty(this._inner, res, EM_R("downloadURL"), EM_L(url));
  6376.     this._setProperty(this._inner, res, EM_R("type"), EM_I(type));
  6377.     this._setProperty(this._inner, res, EM_R("version"), EM_L(version));
  6378.     this._setProperty(this._inner, res, EM_R("incompatibleUpdate"), EM_L("true"));
  6379.     this._setProperty(this._inner, res, EM_R("description"), EM_L(updateMsg));
  6380.  
  6381.     var ctr = getContainer(this._inner, this._itemRoot);
  6382.     if (ctr.IndexOf(res) == -1)
  6383.       ctr.AppendElement(res);
  6384.  
  6385.     this.Flush();
  6386.   },
  6387.  
  6388.   /**
  6389.    * Removes an active download from the appropriate container
  6390.    * @param   url
  6391.    *          The URL string of the active download to be removed
  6392.    */
  6393.   removeDownload: function(url) {
  6394.     var res = gRDF.GetResource(url);
  6395.     var ctr = getContainer(this._inner, this._itemRoot);
  6396.     if (ctr.IndexOf(res) != -1) 
  6397.       ctr.RemoveElement(res, true);
  6398.     this._cleanResource(res);
  6399.     this.Flush();
  6400.   },
  6401.   
  6402.   /**
  6403.    * Write download progress info for a set of items to the Datasource
  6404.    * @param   data
  6405.    *          A JS Object containing progress information for a set of active
  6406.    *          downloads, hashed by URL. Each object has the following properties:
  6407.    *          "state"     An integer value representing the download/install
  6408.    *                      state.
  6409.    *          "progress"  An integer value between 0 and 100 representing 
  6410.    *                      percentage complete
  6411.    */
  6412.   flushProgressInfo: function(data) {
  6413.     for (var url in data) {
  6414.       var res = gRDF.GetResource(url);
  6415.       this._setProperty(this._inner, res, EM_R("state"), EM_I(data[url].state));
  6416.       this._setProperty(this._inner, res, EM_R("progress"), EM_I(data[url].progress));
  6417.     }
  6418.     this.Flush();
  6419.   },   
  6420.   
  6421.   /**
  6422.    * A GUID->location-key hash of items that are visible to the application.
  6423.    * These are items that show up in the Extension/Themes etc UI. If there is
  6424.    * an instance of the same item installed in Install Locations of differing 
  6425.    * profiles, the item at the highest priority location will appear in this 
  6426.    * list.
  6427.    */
  6428.   visibleItems: { },
  6429.   
  6430.   /**
  6431.    * Walk the list of installed items and determine what the visible list is, 
  6432.    * based on which items are visible at the highest priority locations. 
  6433.    */  
  6434.   _buildVisibleItemList: function() {
  6435.     var ctr = getContainer(this, this._itemRoot);
  6436.     var items = ctr.GetElements();
  6437.     while (items.hasMoreElements()) {
  6438.       var item = items.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
  6439.       // Resource URIs adopt the format: location-key,item-id
  6440.       var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6441.       this.visibleItems[id] = this.getItemProperty(id, "installLocation");
  6442.     }
  6443.   },
  6444.   
  6445.   /**
  6446.    * Updates an item's location in the visible item list.
  6447.    * @param   id
  6448.    *          The GUID of the item to update
  6449.    * @param   locationKey
  6450.    *          The name of the Install Location where the item is installed.
  6451.    * @param   forceReplace
  6452.    *          true if the new location should be used, regardless of its 
  6453.    *          priority relationship to existing entries, false if the location
  6454.    *          should only be updated if its priority is lower than the existing
  6455.    *          value.
  6456.    */
  6457.   updateVisibleList: function(id, locationKey, forceReplace) {
  6458.     if (id in this.visibleItems && this.visibleItems[id]) {
  6459.       var oldLocation = InstallLocations.get(this.visibleItems[id]);
  6460.       var newLocation = InstallLocations.get(locationKey);
  6461.       if (forceReplace || newLocation.priority < oldLocation.priority) 
  6462.         this.visibleItems[id] = locationKey;
  6463.     }
  6464.     else 
  6465.       this.visibleItems[id] = locationKey;
  6466.   },
  6467.  
  6468.   /**
  6469.    * Load the Extensions Datasource from disk.
  6470.    */
  6471.   loadExtensions: function() {
  6472.     var extensionsFile  = getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS]);
  6473.     this._inner = gRDF.GetDataSourceBlocking(getURLSpecFromFile(extensionsFile));
  6474.  
  6475.     var cu = Components.classes["@mozilla.org/rdf/container-utils;1"]
  6476.                        .getService(Components.interfaces.nsIRDFContainerUtils);
  6477.     cu.MakeSeq(this._inner, this._itemRoot);
  6478.  
  6479.     this._buildVisibleItemList();
  6480.   },
  6481.   
  6482.   /**
  6483.    * A hash of Addon IDs that we are currently looking for updates to. 
  6484.    */
  6485.   _updateURLs: { },
  6486.   
  6487.   /**
  6488.    * See nsIExtensionManager.idl
  6489.    */
  6490.   onUpdateStarted: function() {
  6491.     LOG("Datasource: Update Started");
  6492.   },
  6493.   
  6494.   /**
  6495.    * See nsIExtensionManager.idl
  6496.    */
  6497.   onUpdateEnded: function() {
  6498.     LOG("Datasource: Update Ended");
  6499.     this._updateURLs = { };
  6500.   },
  6501.   
  6502.   /**
  6503.    * See nsIExtensionManager.idl
  6504.    */
  6505.   onAddonUpdateStarted: function(addon) {
  6506.     LOG("Datasource: Addon Update Started: " + addon.id);
  6507.     this._updateURLs[addon.id] = addon.id;
  6508.     this.updateProperty(addon.id, "availableUpdateURL");
  6509.     this.updateProperty(addon.id, "displayDescription");
  6510.   },
  6511.   
  6512.   /**
  6513.    * See nsIExtensionManager.idl
  6514.    */
  6515.   onAddonUpdateEnded: function(addon, status) {
  6516.     LOG("Datasource: Addon Update Ended: " + addon.id + ", status: " + status);
  6517.     this._updateURLs[addon.id] = status;
  6518.     var url = null, hash = null, version = null;
  6519.     var updateAvailable = status == nsIAddonUpdateCheckListener.STATUS_UPDATE;
  6520.     if (updateAvailable) {
  6521.       url = EM_L(addon.xpiURL);
  6522.       if (addon.xpiHash)
  6523.         hash = EM_L(addon.xpiHash);
  6524.       version = EM_L(addon.version);
  6525.     }
  6526.     this.setItemProperty(addon.id, EM_R("availableUpdateURL"), url);
  6527.     this.setItemProperty(addon.id, EM_R("availableUpdateHash"), hash);
  6528.     this.setItemProperty(addon.id, EM_R("availableUpdateVersion"), version);
  6529.     this.updateProperty(addon.id, "availableUpdateURL");
  6530.     this.updateProperty(addon.id, "displayDescription");
  6531.   },
  6532.  
  6533.   /////////////////////////////////////////////////////////////////////////////
  6534.   // nsIRDFDataSource
  6535.   get URI() {
  6536.     return "rdf:extensions";
  6537.   },
  6538.   
  6539.   GetSource: function(property, target, truthValue) {
  6540.     return this._inner.GetSource(property, target, truthValue);
  6541.   },
  6542.   
  6543.   GetSources: function(property, target, truthValue) {
  6544.     return this._inner.GetSources(property, target, truthValue);
  6545.   },
  6546.   
  6547.   /**
  6548.    * Gets an URL to a theme's image file
  6549.    * @param   item
  6550.    *          The RDF Resource representing the item 
  6551.    * @param   fileName
  6552.    *          The file to locate a URL for
  6553.    * @param   fallbackURL
  6554.    *          If the location fails, supply this URL instead
  6555.    * @returns An RDF Resource to the URL discovered, or the fallback
  6556.    *          if the discovery failed. 
  6557.    */
  6558.   _getThemeImageURL: function(item, fileName, fallbackURL) {
  6559.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6560.     var installLocation = this._em.getInstallLocation(id);
  6561.     var file = installLocation.getItemFile(id, fileName)
  6562.     if (file.exists())
  6563.       return gRDF.GetResource(getURLSpecFromFile(file));
  6564.  
  6565.     if (id == stripPrefix(RDFURI_DEFAULT_THEME, PREFIX_ITEM_URI)) {
  6566.       var jarFile = getFile(KEY_APPDIR, [DIR_CHROME, FILE_DEFAULT_THEME_JAR]);
  6567.       var url = "jar:" + getURLSpecFromFile(jarFile) + "!/" + fileName;
  6568.       return gRDF.GetResource(url);
  6569.     }
  6570.  
  6571.     return fallbackURL ? gRDF.GetResource(fallbackURL) : null;
  6572.   },
  6573.  
  6574.   /**
  6575.    * Get the em:iconURL property (icon url of the item)
  6576.    */
  6577.   _rdfGet_iconURL: function(item, property) {
  6578.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6579.     var type = this.getItemProperty(id, "type");
  6580.     if (type != -1 && type & nsIUpdateItem.TYPE_EXTENSION) {
  6581.       var hasIconURL = this._inner.hasArcOut(item, property);
  6582.       // If the download entry doesn't have a IconURL property, use a
  6583.       // generic icon URL instead.
  6584.       if (!hasIconURL || this.getItemProperty(id, "disabled") == "true")
  6585.         return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  6586.       var iconURL = this._inner.GetTarget(item, property, true);
  6587.       iconURL = iconURL.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
  6588.       var uri = newURI(iconURL);
  6589.       try {
  6590.         var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
  6591.                             .getService(Components.interfaces.nsIChromeRegistry);
  6592.         cr.convertChromeURL(uri);
  6593.       }
  6594.       catch(e) {
  6595.         // bogus URI, supply a generic icon. 
  6596.         return gRDF.GetResource(URI_GENERIC_ICON_XPINSTALL);
  6597.       }
  6598.     }
  6599.     else if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  6600.       return this._getThemeImageURL(item, "icon.png", URI_GENERIC_ICON_THEME);
  6601.     return null;
  6602.   },
  6603.   
  6604.   /**
  6605.    * Get the em:previewImage property (preview image of the item)
  6606.    */
  6607.   _rdfGet_previewImage: function(item, property) {
  6608.     var type = this.getItemProperty(stripPrefix(item.Value, PREFIX_ITEM_URI), "type");
  6609.     if (type != -1 && type & nsIUpdateItem.TYPE_THEME)
  6610.       return this._getThemeImageURL(item, "preview.png", null);
  6611.     return null;
  6612.   },
  6613.   
  6614.   /**
  6615.    * If we're in safe mode, the item is disabled by the user or app, or the
  6616.    * item is to be upgraded force the generic about dialog for the item.
  6617.    */
  6618.   _rdfGet_aboutURL: function(item, property) {
  6619.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6620.     if (inSafeMode() || this.getItemProperty(id, "disabled") == "true" ||
  6621.         this.getItemProperty(id, "opType") == OP_NEEDS_UPGRADE)
  6622.       return EM_L("");
  6623.  
  6624.     return null;
  6625.   },
  6626.  
  6627.   /**
  6628.    * Get the em:compatible property (whether or not this item is compatible)
  6629.    */
  6630.   _rdfGet_compatible: function(item, property) {
  6631.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6632.     var targetAppInfo = this.getTargetApplicationInfo(id, this);
  6633.     if (!targetAppInfo)
  6634.       return EM_L("false");
  6635.     getVersionChecker();
  6636.     
  6637.     var appVersion = getPref("getCharPref", PREF_EM_APP_EXTENSIONS_VERSION,
  6638.                              gApp.version);
  6639.     if (gVersionChecker.compare(targetAppInfo.maxVersion, appVersion) < 0 || 
  6640.         gVersionChecker.compare(appVersion, targetAppInfo.minVersion) < 0) {
  6641.       // OK, this item is incompatible. 
  6642.       return EM_L("false");
  6643.     }
  6644.     return EM_L("true");
  6645.   }, 
  6646.   
  6647.   /** 
  6648.    * Gets the em:availableUpdateURL - the URL to an XPI update package, if
  6649.    * present, or a literal string "novalue" if there is no update XPI URL.
  6650.    */
  6651.   _rdfGet_availableUpdateURL: function(item, property) {
  6652.     var value = this._inner.GetTarget(item, property, true);
  6653.     if (!value) 
  6654.       return EM_L("none");
  6655.     return value;
  6656.   },
  6657.  
  6658.   /**
  6659.    * Get the em:displayDescription property (description that is displayed 
  6660.    * in the UI - not always the info description of the item - sometimes
  6661.    * a message like "will be uninstalled after restart" etc)
  6662.    */ 
  6663.   _rdfGet_displayDescription: function(item, property) {
  6664.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6665.  
  6666.     var extensionsStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
  6667.     var itemName = this.getItemProperty(id, "name");
  6668.     var opType = this.getItemProperty(id, "opType");
  6669.     
  6670.     function getLiteral(key, strings) {
  6671.       return EM_L(extensionsStrings.formatStringFromName(key, 
  6672.                   strings, strings.length));
  6673.     }
  6674.     if (id in this._updateURLs && this._updateURLs[id]) {
  6675.       switch (this._updateURLs[id]) {
  6676.       case id:
  6677.         return getLiteral("updatingMessage", [itemName]);
  6678.       case nsIAddonUpdateCheckListener.STATUS_APP_MANAGED:
  6679.       case nsIAddonUpdateCheckListener.STATUS_NO_UPDATE:
  6680.         return getLiteral("updateNoUpdateMessage", [itemName]);
  6681.       case nsIAddonUpdateCheckListener.STATUS_VERSIONINFO:
  6682.         if (opType == OP_NEEDS_UPGRADE || opType == OP_NEEDS_INSTALL)
  6683.           break;
  6684.         return getLiteral("updateCompatibilityMessage", [itemName]);
  6685.       case nsIAddonUpdateCheckListener.STATUS_FAILURE:
  6686.         return getLiteral("updateErrorMessage", [itemName]);
  6687.       case nsIAddonUpdateCheckListener.STATUS_DISABLED:
  6688.         return getLiteral("updateDisabledMessage", [itemName]);
  6689.       case nsIAddonUpdateCheckListener.STATUS_READ_ONLY:
  6690.         return getLiteral("updateReadOnlyMessage", []);
  6691.       case nsIAddonUpdateCheckListener.STATUS_NOT_MANAGED:
  6692.         return getLiteral("updateNotManagedMessage", [BundleManager.appName]);
  6693.       }
  6694.     }
  6695.     var node = this._inner.GetTarget(item, EM_R("availableUpdateURL"), true); 
  6696.     if (node) {
  6697.       var version = this.getItemProperty(id, "availableUpdateVersion");
  6698.       return getLiteral("updateAvailableMessage", [itemName, version]);
  6699.     }
  6700.     switch (opType) {
  6701.     case OP_NEEDS_DISABLE:
  6702.       return getLiteral("restartBeforeDisableMessage", [itemName, BundleManager.appName]);
  6703.     case OP_NEEDS_ENABLE:
  6704.       return getLiteral("restartBeforeEnableMessage", [itemName, BundleManager.appName]);
  6705.     case OP_NEEDS_INSTALL:
  6706.       return getLiteral("restartBeforeInstallMessage", [itemName, BundleManager.appName]);
  6707.     case OP_NEEDS_UNINSTALL:
  6708.       return getLiteral("restartBeforeUninstallMessage", [itemName, BundleManager.appName]);
  6709.     case OP_NEEDS_UPGRADE:
  6710.       return getLiteral("restartBeforeUpgradeMessage", [itemName, BundleManager.appName]);
  6711.     }
  6712.  
  6713.     if (this.getItemProperty(id, "appDisabled") == "true" &&
  6714.         this.getItemProperty(id, "compatible") != "true")
  6715.       return getLiteral("incompatibleExtension", [BundleManager.appName, gApp.version]);
  6716.  
  6717.     if (inSafeMode())
  6718.       return getLiteral("disabledBySafeMode", [itemName, BundleManager.appName]);
  6719.  
  6720.     // No special state for this item, so just use the "description" property. 
  6721.     return this.GetTarget(item, EM_R("description"), true);
  6722.   },
  6723.   
  6724.   /**
  6725.    * Get the em:opType property (controls widget state for the EM UI)
  6726.    * from the Startup Cache (e.g. extensions.cache)
  6727.    */
  6728.   _rdfGet_opType: function(item, property) {
  6729.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6730.     var key = this.getItemProperty(id, "installLocation");
  6731.     if (key in StartupCache.entries && id in StartupCache.entries[key] &&
  6732.         StartupCache.entries[key][id])
  6733.       return EM_L(StartupCache.entries[key][id].op);
  6734.     return EM_L(OP_NONE);
  6735.   },
  6736.  
  6737.   /**
  6738.    * Gets a localizable property. Install Manifests are generally only in one 
  6739.    * language, however an item can customize by providing localized prefs in 
  6740.    * the form:
  6741.    *
  6742.    *    extensions.{GUID}.[name|description|creator|homepageURL]
  6743.    *
  6744.    * to specify localized text for each of these properties.
  6745.    */
  6746.   _getLocalizablePropertyValue: function(item, property) {
  6747.     // These are localizable properties that a language pack supplied by the 
  6748.     // Extension may override.          
  6749.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  6750.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  6751.                     stripPrefix(property.Value, PREFIX_NS_EM);
  6752.     try {
  6753.       var value = gPref.getComplexValue(prefName, 
  6754.                                         Components.interfaces.nsIPrefLocalizedString);
  6755.       if (value.data) 
  6756.         return EM_L(value.data);
  6757.     }
  6758.     catch (e) {
  6759.     }
  6760.     return null;
  6761.   },
  6762.   
  6763.   /**
  6764.    * Get the em:name property (name of the item)
  6765.    */
  6766.   _rdfGet_name: function(item, property) {
  6767.     return this._getLocalizablePropertyValue(item, property);
  6768.   },
  6769.   
  6770.   /**
  6771.    * Get the em:description property (description of the item)
  6772.    */
  6773.   _rdfGet_description: function(item, property) {
  6774.     return this._getLocalizablePropertyValue(item, property);
  6775.   },
  6776.   
  6777.   /**
  6778.    * Get the em:creator property (creator of the item)
  6779.    */
  6780.   _rdfGet_creator: function(item, property) { 
  6781.     return this._getLocalizablePropertyValue(item, property);
  6782.   },
  6783.   
  6784.   /**
  6785.    * Get the em:homepageURL property (homepage URL of the item)
  6786.    */
  6787.   _rdfGet_homepageURL: function(item, property) {
  6788.     return this._getLocalizablePropertyValue(item, property);
  6789.   },
  6790.  
  6791.   /**
  6792.    * Get the em:disabled property. This will be true if the item has a
  6793.    * appDisabled or a userDisabled property that is true as long as it is not
  6794.    * about to be disabled.
  6795.    */
  6796.   _rdfGet_disabled: function(item, property) {
  6797.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6798.     if ((this.getItemProperty(id, "userDisabled") == "true" ||
  6799.         this.getItemProperty(id, "appDisabled") == "true") &&
  6800.         this.getItemProperty(id, "opType") != OP_NEEDS_DISABLE)
  6801.       return EM_L("true");
  6802.  
  6803.     // Migrate disabled in the extensions datasource to userDisabled or
  6804.     // appDisabled as appropriate.
  6805.     var oldDisabled = this._inner.GetTarget(item, property, true);
  6806.     if (oldDisabled instanceof Components.interfaces.nsIRDFLiteral) {
  6807.       this._inner.Unassert(item, property, oldDisabled);
  6808.       if (this.getItemProperty(id, "compatible") == "true")
  6809.         this.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
  6810.       else
  6811.         this.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
  6812.  
  6813.       return EM_L("true");
  6814.     }
  6815.  
  6816.     return EM_L("false");
  6817.   },
  6818.  
  6819.   /**
  6820.    * Get the em:updateable property - this specifies whether the item is
  6821.    * allowed to be updated
  6822.    */
  6823.   _rdfGet_updateable: function(item, property) {
  6824.     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
  6825.     var opType = this.getItemProperty(id, "opType");
  6826.     if (opType == OP_NEEDS_INSTALL || opType == OP_NEEDS_UNINSTALL ||
  6827.         opType == OP_NEEDS_UPGRADE ||
  6828.         this.getItemProperty(id, "appManaged") == "true")
  6829.       return EM_L("false");
  6830.  
  6831.     try {
  6832.       if (!gPref.getBoolPref(PREF_EM_ITEM_UPDATE_ENABLED.replace(/%UUID%/, id)))
  6833.         return EM_L("false");
  6834.     }
  6835.     catch (e) { }
  6836.  
  6837.     var installLocation = InstallLocations.get(this.getInstallLocationKey(id));
  6838.     if (!installLocation || !installLocation.canAccess)
  6839.       return EM_L("false");
  6840.  
  6841.     return EM_L("true");
  6842.   },
  6843.  
  6844.   /**
  6845.    * See nsIRDFDataSource.idl
  6846.    */
  6847.   GetTarget: function(source, property, truthValue) {
  6848.     if (!source)
  6849.       return null;
  6850.       
  6851.     var target = null;
  6852.     var getter = "_rdfGet_" + stripPrefix(property.Value, PREFIX_NS_EM);
  6853.     if (getter in this)
  6854.       target = this[getter](source, property);
  6855.  
  6856.     return target || this._inner.GetTarget(source, property, truthValue);
  6857.   },
  6858.   
  6859.   /**
  6860.    * Gets an enumeration of values of a localizable property. Install Manifests
  6861.    * are generally only in one language, however an item can customize by 
  6862.    * providing localized prefs in the form:
  6863.    *
  6864.    *    extensions.{GUID}.[contributor].1
  6865.    *    extensions.{GUID}.[contributor].2
  6866.    *    extensions.{GUID}.[contributor].3
  6867.    *    ...
  6868.    *
  6869.    * to specify localized text for each of these properties.
  6870.    */
  6871.   _getLocalizablePropertyValues: function(item, property) {
  6872.     // These are localizable properties that a language pack supplied by the 
  6873.     // Extension may override.          
  6874.     var values = [];
  6875.     var prefName = PREF_EM_EXTENSION_FORMAT.replace(/%UUID%/, 
  6876.                     stripPrefix(item.Value, PREFIX_ITEM_URI)) + 
  6877.                     stripPrefix(property.Value, PREFIX_NS_EM);
  6878.     var i = 0;
  6879.     do {
  6880.       try {
  6881.         var value = gPref.getComplexValue(prefName + "." + ++i, 
  6882.                                           Components.interfaces.nsIPrefLocalizedString);
  6883.         if (value.data) 
  6884.           values.push(EM_L(value.data));
  6885.       }
  6886.       catch (e) {
  6887.         try {
  6888.           var value = gPref.getComplexValue(prefName, 
  6889.                                             Components.interfaces.nsIPrefLocalizedString);
  6890.           if (value.data) 
  6891.             values.push(EM_L(value.data));
  6892.         }
  6893.         catch (e) {
  6894.         }
  6895.         break;
  6896.       }
  6897.     }
  6898.     while (1);
  6899.     return values;
  6900.   },
  6901.   
  6902.   /**
  6903.    * Get the em:name property (name of the item - template builder calls
  6904.    * GetTargets on this one for some reason).
  6905.    */
  6906.   _rdfGets_name: function(item, property) {
  6907.     return this._getLocalizablePropertyValues(item, property);
  6908.   },
  6909.   
  6910.   /**
  6911.    * Get the em:contributor property (contributors to the extension)
  6912.    */
  6913.   _rdfGets_contributor: function(item, property) {
  6914.     return this._getLocalizablePropertyValues(item, property); 
  6915.   },
  6916.   
  6917.   /**
  6918.    * See nsIRDFDataSource.idl
  6919.    */
  6920.   GetTargets: function(source, property, truthValue) {
  6921.     if (!source)
  6922.       return null;
  6923.       
  6924.     var ary = null;
  6925.     var getter = "_rdfGets_" + stripPrefix(source.Value, EM_NS);
  6926.     if (getter in this)
  6927.       ary = this[getter](source, property);
  6928.     
  6929.     return ary ? new ArrayEnumerator(ary) 
  6930.                : this._inner.GetTargets(source, property, truthValue);
  6931.   },
  6932.   
  6933.   Assert: function(source, property, target, truthValue) {
  6934.     this._inner.Assert(source, property, target, truthValue);
  6935.   },
  6936.   
  6937.   Unassert: function(source, property, target) {
  6938.     this._inner.Unassert(source, property, target);
  6939.   },
  6940.   
  6941.   Change: function(source, property, oldTarget, newTarget) {
  6942.     this._inner.Change(source, property, oldTarget, newTarget);
  6943.   },
  6944.  
  6945.   Move: function(oldSource, newSource, property, target) {
  6946.     this._inner.Move(oldSource, newSource, property, target);
  6947.   },
  6948.   
  6949.   HasAssertion: function(source, property, target, truthValue) {
  6950.     if (!source || !property || !target)
  6951.       return false;
  6952.     return this._inner.HasAssertion(source, property, target, truthValue);
  6953.   },
  6954.   
  6955.   _observers: [],
  6956.   AddObserver: function(observer) {
  6957.     for (var i = 0; i < this._observers.length; ++i) {
  6958.       if (this._observers[i] == observer) 
  6959.         return;
  6960.     }
  6961.     this._observers.push(observer);
  6962.     this._inner.AddObserver(observer);
  6963.   },
  6964.   
  6965.   RemoveObserver: function(observer) {
  6966.     for (var i = 0; i < this._observers.length; ++i) {
  6967.       if (this._observers[i] == observer) 
  6968.         this._observers.splice(i, 1);
  6969.     }
  6970.     this._inner.RemoveObserver(observer);
  6971.   },
  6972.   
  6973.   ArcLabelsIn: function(node) {
  6974.     return this._inner.ArcLabelsIn(node);
  6975.   },
  6976.   
  6977.   ArcLabelsOut: function(source) {
  6978.     return this._inner.ArcLabelsOut(source);
  6979.   },
  6980.   
  6981.   GetAllResources: function() {
  6982.     return this._inner.GetAllResources();
  6983.   },
  6984.   
  6985.   IsCommandEnabled: function(sources, command, arguments) {
  6986.     return this._inner.IsCommandEnabled(sources, command, arguments);
  6987.   },
  6988.   
  6989.   DoCommand: function(sources, command, arguments) {
  6990.     this._inner.DoCommand(sources, command, arguments);
  6991.   },
  6992.   
  6993.   GetAllCmds: function(source) {
  6994.     return this._inner.GetAllCmds(source);
  6995.   },
  6996.   
  6997.   hasArcIn: function(node, arc) {
  6998.     return this._inner.hasArcIn(node, arc);
  6999.   },
  7000.   
  7001.   hasArcOut: function(source, arc) {
  7002.     return this._inner.hasArcOut(source, arc);
  7003.   },
  7004.   
  7005.   beginUpdateBatch: function() {
  7006.     return this._inner.beginUpdateBatch();
  7007.   },
  7008.   
  7009.   endUpdateBatch: function() {
  7010.     return this._inner.endUpdateBatch();
  7011.   },
  7012.   
  7013.   /**
  7014.    * See nsIRDFRemoteDataSource.idl
  7015.    */
  7016.   get loaded() {
  7017.     throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  7018.   },
  7019.   
  7020.   Init: function(uri) {
  7021.   },
  7022.   
  7023.   Refresh: function(blocking) {
  7024.   },
  7025.   
  7026.   Flush: function() {
  7027.     if (this._inner instanceof Components.interfaces.nsIRDFRemoteDataSource)
  7028.       this._inner.Flush();
  7029.   },
  7030.   
  7031.   FlushTo: function(uri) {
  7032.   },
  7033.   
  7034.   /**
  7035.    * See nsISupports.idl
  7036.    */
  7037.   QueryInterface: function(iid) {
  7038.     if (!iid.equals(Components.interfaces.nsIRDFDataSource) &&
  7039.         !iid.equals(Components.interfaces.nsIRDFRemoteDataSource) && 
  7040.         !iid.equals(Components.interfaces.nsISupports))
  7041.       throw Components.results.NS_ERROR_NO_INTERFACE;
  7042.     return this;
  7043.   }
  7044. };
  7045.  
  7046. function UpdateItem () {
  7047. }
  7048. UpdateItem.prototype = {
  7049.   /**
  7050.    * See nsIUpdateService.idl
  7051.    */
  7052.   init: function(id, version, installLocationKey, minAppVersion, maxAppVersion,
  7053.                  name, downloadURL, xpiHash, iconURL, updateURL, type) {
  7054.     this._id                  = id;
  7055.     this._version             = version;
  7056.     this._installLocationKey  = installLocationKey;
  7057.     this._minAppVersion       = minAppVersion;
  7058.     this._maxAppVersion       = maxAppVersion;
  7059.     this._name                = name;
  7060.     this._downloadURL         = downloadURL;
  7061.     this._xpiHash             = xpiHash;
  7062.     this._iconURL             = iconURL;
  7063.     this._updateURL           = updateURL;
  7064.     this._type                = type;
  7065.   },
  7066.   
  7067.   /**
  7068.    * See nsIUpdateService.idl
  7069.    */
  7070.   get id()                { return this._id;                },
  7071.   get version()           { return this._version;           },
  7072.   get installLocationKey(){ return this._installLocationKey;},
  7073.   get minAppVersion()     { return this._minAppVersion;     },
  7074.   get maxAppVersion()     { return this._maxAppVersion;     },
  7075.   get name()              { return this._name;              },
  7076.   get xpiURL()            { return this._downloadURL;       },
  7077.   get xpiHash()           { return this._xpiHash;           },
  7078.   get iconURL()           { return this._iconURL            },
  7079.   get updateRDF()         { return this._updateURL;         },
  7080.   get type()              { return this._type;              },
  7081.  
  7082.   /**
  7083.    * See nsIUpdateService.idl
  7084.    */
  7085.   get objectSource() {
  7086.     return { id                 : this._id, 
  7087.              version            : this._version, 
  7088.              installLocationKey : this._installLocationKey,
  7089.              minAppVersion      : this._minAppVersion,
  7090.              maxAppVersion      : this._maxAppVersion,
  7091.              name               : this._name, 
  7092.              xpiURL             : this._downloadURL, 
  7093.              xpiHash            : this._xpiHash,
  7094.              iconURL            : this._iconURL, 
  7095.              updateRDF          : this._updateURL,
  7096.              type               : this._type 
  7097.            }.toSource();
  7098.   },
  7099.   
  7100.   /**
  7101.    * See nsISupports.idl
  7102.    */
  7103.   QueryInterface: function(iid) {
  7104.     if (!iid.equals(Components.interfaces.nsIUpdateItem) &&
  7105.         !iid.equals(Components.interfaces.nsISupports))
  7106.       throw Components.results.NS_ERROR_NO_INTERFACE;
  7107.     return this;
  7108.   }
  7109. };
  7110.  
  7111. var gModule = {
  7112.   registerSelf: function(componentManager, fileSpec, location, type) {
  7113.     componentManager = componentManager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  7114.     
  7115.     for (var key in this._objects) {
  7116.       var obj = this._objects[key];
  7117.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  7118.                                                fileSpec, location, type);
  7119.     }
  7120.  
  7121.     // Make the Extension Manager a startup observer
  7122.     var categoryManager = Components.classes["@mozilla.org/categorymanager;1"]
  7123.                                     .getService(Components.interfaces.nsICategoryManager);
  7124.     categoryManager.addCategoryEntry("app-startup", this._objects.manager.className,
  7125.                                      "service," + this._objects.manager.contractID, 
  7126.                                      true, true, null);
  7127.   },
  7128.   
  7129.   getClassObject: function(componentManager, cid, iid) {
  7130.     if (!iid.equals(Components.interfaces.nsIFactory))
  7131.       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  7132.  
  7133.     for (var key in this._objects) {
  7134.       if (cid.equals(this._objects[key].CID))
  7135.         return this._objects[key].factory;
  7136.     }
  7137.     
  7138.     throw Components.results.NS_ERROR_NO_INTERFACE;
  7139.   },
  7140.   
  7141.   _makeFactory: #1= function(ctor) {
  7142.     return { 
  7143.              createInstance: function (outer, iid) {
  7144.                if (outer != null)
  7145.                  throw Components.results.NS_ERROR_NO_AGGREGATION;
  7146.                return (new ctor()).QueryInterface(iid);
  7147.              } 
  7148.            };  
  7149.   },
  7150.   
  7151.   _objects: {
  7152.     manager: { CID        : ExtensionManager.prototype.classID,
  7153.                contractID : ExtensionManager.prototype.contractID,
  7154.                className  : ExtensionManager.prototype.classDescription,
  7155.                factory    : #1#(ExtensionManager)
  7156.              },
  7157.     item:    { CID        : Components.ID("{F3294B1C-89F4-46F8-98A0-44E1EAE92518}"),
  7158.                contractID : "@mozilla.org/updates/item;1",
  7159.                className  : "Update Item",
  7160.                factory    : #1#(UpdateItem)
  7161.              }
  7162.    },
  7163.  
  7164.   canUnload: function(componentManager) {
  7165.     return true;
  7166.   }
  7167. };
  7168.  
  7169. function NSGetModule(compMgr, fileSpec) {
  7170.   return gModule;
  7171. }
  7172.  
  7173.