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