home *** CD-ROM | disk | FTP | other *** search
/ PC World 2007 December / PCWorld_2007-12_cd.bin / audio-video / songbird / Songbird_0.3_windows-i686.exe / xulrunner / components / nsExtensionManager.js < prev    next >
Text File  |  2007-10-26  |  321KB  |  8,498 lines

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