bug 461444 - remove cases of excessive recursion in makefiles r=ted
authorMitchell Field <mitch_1_2@live.com.au>
Thu, 14 Jan 2010 08:23:00 -0500
changeset 37488 83eac7a5262a398d7336b6d8fed7dc3bf1608e90
parent 37487 b020c6b1588daeda71e3d3cfc30ff09dbc885bad
child 37489 71f78cfd324d5d20f7e02497fc7ad3434ee8640d
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted
bugs461444
milestone1.9.3a1pre
bug 461444 - remove cases of excessive recursion in makefiles r=ted
toolkit/mozapps/downloads/DownloadLastDir.jsm
toolkit/mozapps/downloads/DownloadUtils.jsm
toolkit/mozapps/downloads/Makefile.in
toolkit/mozapps/downloads/nsHelperAppDlg.js.in
toolkit/mozapps/downloads/src/DownloadLastDir.jsm
toolkit/mozapps/downloads/src/DownloadUtils.jsm
toolkit/mozapps/downloads/src/Makefile.in
toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
toolkit/mozapps/extensions/LightweightThemeManager.jsm
toolkit/mozapps/extensions/Makefile.in
toolkit/mozapps/extensions/nsAddonRepository.js
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/nsExtensionManager.js.in
toolkit/mozapps/extensions/nsIAddonRepository.idl
toolkit/mozapps/extensions/nsIExtensionManager.idl
toolkit/mozapps/extensions/public/Makefile.in
toolkit/mozapps/extensions/public/nsIAddonRepository.idl
toolkit/mozapps/extensions/public/nsIExtensionManager.idl
toolkit/mozapps/extensions/src/LightweightThemeManager.jsm
toolkit/mozapps/extensions/src/Makefile.in
toolkit/mozapps/extensions/src/nsAddonRepository.js
toolkit/mozapps/extensions/src/nsBlocklistService.js
toolkit/mozapps/extensions/src/nsExtensionManager.js.in
toolkit/mozapps/handling/Makefile.in
toolkit/mozapps/handling/nsContentDispatchChooser.js
toolkit/mozapps/handling/src/Makefile.in
toolkit/mozapps/handling/src/nsContentDispatchChooser.js
toolkit/mozapps/shared/CertUtils.jsm
toolkit/mozapps/shared/FileUtils.jsm
toolkit/mozapps/shared/Makefile.in
toolkit/mozapps/shared/src/CertUtils.jsm
toolkit/mozapps/shared/src/FileUtils.jsm
toolkit/mozapps/shared/src/Makefile.in
toolkit/toolkit-makefiles.sh
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/downloads/DownloadLastDir.jsm
@@ -0,0 +1,131 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Download Manager Utility Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Ehsan Akhgari <ehsan.akhgari@gmail.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * The behavior implemented by gDownloadLastDir is documented here.
+ *
+ * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir
+ * preference to store the last used download directory. The first time the user
+ * switches into the private browsing mode, the last download directory is
+ * preserved to the pref value, but if the user switches to another directory
+ * during the private browsing mode, that directory is not stored in the pref,
+ * and will be merely kept in memory.  When leaving the private browsing mode,
+ * this in-memory value will be discarded, and the last download directory
+ * will be reverted to the pref value.
+ *
+ * Both the pref and the in-memory value will be cleared when clearing the
+ * browsing history.  This effectively changes the last download directory
+ * to the default download directory on each platform.
+ */
+
+const LAST_DIR_PREF = "browser.download.lastDir";
+const PBSVC_CID = "@mozilla.org/privatebrowsing;1";
+const nsILocalFile = Components.interfaces.nsILocalFile;
+
+var EXPORTED_SYMBOLS = [ "gDownloadLastDir" ];
+
+let pbSvc = null;
+if (PBSVC_CID in Components.classes) {
+  pbSvc = Components.classes[PBSVC_CID]
+                    .getService(Components.interfaces.nsIPrivateBrowsingService);
+}
+let prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+
+let observer = {
+  QueryInterface: function (aIID) {
+    if (aIID.equals(Components.interfaces.nsIObserver) ||
+        aIID.equals(Components.interfaces.nsISupports) ||
+        aIID.equals(Components.interfaces.nsISupportsWeakReference))
+      return this;
+    throw Components.results.NS_NOINTERFACE;
+  },
+  observe: function (aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case "private-browsing":
+        if (aData == "enter")
+          gDownloadLastDirFile = readLastDirPref();
+        else if (aData == "exit")
+          gDownloadLastDirFile = null;
+        break;
+      case "browser:purge-session-history":
+        gDownloadLastDirFile = null;
+        if (prefSvc.prefHasUserValue(LAST_DIR_PREF))
+          prefSvc.clearUserPref(LAST_DIR_PREF);
+        break;
+    }
+  }
+};
+
+let os = Components.classes["@mozilla.org/observer-service;1"]
+                   .getService(Components.interfaces.nsIObserverService);
+os.addObserver(observer, "private-browsing", true);
+os.addObserver(observer, "browser:purge-session-history", true);
+
+function readLastDirPref() {
+  try {
+    return prefSvc.getComplexValue(LAST_DIR_PREF, nsILocalFile);
+  }
+  catch (e) {
+    return null;
+  }
+}
+
+let gDownloadLastDirFile = readLastDirPref();
+let gDownloadLastDir = {
+  get file() {
+    if (gDownloadLastDirFile && !gDownloadLastDirFile.exists())
+      gDownloadLastDirFile = null;
+
+    if (pbSvc && pbSvc.privateBrowsingEnabled)
+      return gDownloadLastDirFile;
+    else
+      return readLastDirPref();
+  },
+  set file(val) {
+    if (pbSvc && pbSvc.privateBrowsingEnabled) {
+      if (val instanceof Components.interfaces.nsIFile)
+        gDownloadLastDirFile = val.clone();
+      else
+        gDownloadLastDirFile = null;
+    } else {
+      if (val instanceof Components.interfaces.nsIFile)
+        prefSvc.setComplexValue(LAST_DIR_PREF, nsILocalFile, val);
+      else if (prefSvc.prefHasUserValue(LAST_DIR_PREF))
+        prefSvc.clearUserPref(LAST_DIR_PREF);
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/downloads/DownloadUtils.jsm
@@ -0,0 +1,508 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Download Manager Utility Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Edward Lee <edward.lee@engineering.uiuc.edu>.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var EXPORTED_SYMBOLS = [ "DownloadUtils" ];
+
+/**
+ * This module provides the DownloadUtils object which contains useful methods
+ * for downloads such as displaying file sizes, transfer times, and download
+ * locations.
+ *
+ * List of methods:
+ *
+ * [string status, double newLast]
+ * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes,
+ *                   [optional] double aSpeed, [optional] double aLastSec)
+ *
+ * string progress
+ * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes)
+ *
+ * [string timeLeft, double newLast]
+ * getTimeLeft(double aSeconds, [optional] double aLastSec)
+ *
+ * [string displayHost, string fullHost]
+ * getURIHost(string aURIString)
+ *
+ * [double convertedBytes, string units]
+ * convertByteUnits(int aBytes)
+ *
+ * [int time, string units, int subTime, string subUnits]
+ * convertTimeUnits(double aSecs)
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+__defineGetter__("PluralForm", function() {
+  delete this.PluralForm;
+  Cu.import("resource://gre/modules/PluralForm.jsm");
+  return PluralForm;
+});
+
+const kDownloadProperties =
+  "chrome://mozapps/locale/downloads/downloads.properties";
+
+// These strings will be converted to the corresponding ones from the string
+// bundle on use
+let kStrings = {
+  statusFormat: "statusFormat2",
+  transferSameUnits: "transferSameUnits",
+  transferDiffUnits: "transferDiffUnits",
+  transferNoTotal: "transferNoTotal",
+  timePair: "timePair",
+  timeLeftSingle: "timeLeftSingle",
+  timeLeftDouble: "timeLeftDouble",
+  timeFewSeconds: "timeFewSeconds",
+  timeUnknown: "timeUnknown",
+  doneScheme: "doneScheme",
+  doneFileScheme: "doneFileScheme",
+  units: ["bytes", "kilobyte", "megabyte", "gigabyte"],
+  // Update timeSize in convertTimeUnits if changing the length of this array
+  timeUnits: ["seconds", "minutes", "hours", "days"],
+};
+
+// This object will lazily load the strings defined in kStrings
+let gStr = {
+  /**
+   * Initialize lazy string getters
+   */
+  _init: function()
+  {
+    // Make each "name" a lazy-loading string that knows how to load itself. We
+    // need to locally scope name and value to keep them around for the getter.
+    for (let [name, value] in Iterator(kStrings))
+      let ([n, v] = [name, value])
+        gStr.__defineGetter__(n, function() gStr._getStr(n, v));
+  },
+
+  /**
+   * Convert strings to those in the string bundle. This lazily loads the
+   * string bundle *once* only when used the first time.
+   */
+  get _getStr()
+  {
+    // Delete the getter to be overwritten
+    delete gStr._getStr;
+
+    // Lazily load the bundle into the closure on first call to _getStr
+    let getStr = Cc["@mozilla.org/intl/stringbundle;1"].
+                 getService(Ci.nsIStringBundleService).
+                 createBundle(kDownloadProperties).
+                 GetStringFromName;
+
+    // _getStr is a function that sets string "name" to stringbundle's "value"
+    return gStr._getStr = function(name, value) {
+      // Delete the getter to be overwritten
+      delete gStr[name];
+
+      try {
+        // "name" is a string or array of the stringbundle-loaded "value"
+        return gStr[name] = typeof value == "string" ?
+                            getStr(value) :
+                            value.map(getStr);
+      } catch (e) {
+        log(["Couldn't get string '", name, "' from property '", value, "'"]);
+        // Don't return anything (undefined), and because we deleted ourselves,
+        // future accesses will also be undefined
+      }
+    };
+  },
+};
+// Initialize the lazy string getters!
+gStr._init();
+
+// Keep track of at most this many second/lastSec pairs so that multiple calls
+// to getTimeLeft produce the same time left
+const kCachedLastMaxSize = 10;
+let gCachedLast = [];
+
+let DownloadUtils = {
+  /**
+   * Generate a full status string for a download given its current progress,
+   * total size, speed, last time remaining
+   *
+   * @param aCurrBytes
+   *        Number of bytes transferred so far
+   * @param [optional] aMaxBytes
+   *        Total number of bytes or -1 for unknown
+   * @param [optional] aSpeed
+   *        Current transfer rate in bytes/sec or -1 for unknown
+   * @param [optional] aLastSec
+   *        Last time remaining in seconds or Infinity for unknown
+   * @return A pair: [download status text, new value of "last seconds"]
+   */
+  getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes,
+                                                   aSpeed, aLastSec)
+  {
+    if (aMaxBytes == null)
+      aMaxBytes = -1;
+    if (aSpeed == null)
+      aSpeed = -1;
+    if (aLastSec == null)
+      aLastSec = Infinity;
+
+    // Calculate the time remaining if we have valid values
+    let seconds = (aSpeed > 0) && (aMaxBytes > 0) ?
+      (aMaxBytes - aCurrBytes) / aSpeed : -1;
+
+    // Update the bytes transferred and bytes total
+    let status;
+    let (transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes)) {
+      // Insert 1 is the download progress
+      status = replaceInsert(gStr.statusFormat, 1, transfer);
+    }
+
+    // Update the download rate
+    let ([rate, unit] = DownloadUtils.convertByteUnits(aSpeed)) {
+      // Insert 2 is the download rate
+      status = replaceInsert(status, 2, rate);
+      // Insert 3 is the |unit|/sec
+      status = replaceInsert(status, 3, unit);
+    }
+
+    // Update time remaining
+    let ([timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec)) {
+      // Insert 4 is the time remaining
+      status = replaceInsert(status, 4, timeLeft);
+
+      return [status, newLast];
+    }
+  },
+
+  /**
+   * Generate the transfer progress string to show the current and total byte
+   * size. Byte units will be as large as possible and the same units for
+   * current and max will be supressed for the former.
+   *
+   * @param aCurrBytes
+   *        Number of bytes transferred so far
+   * @param [optional] aMaxBytes
+   *        Total number of bytes or -1 for unknown
+   * @return The transfer progress text
+   */
+  getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes)
+  {
+    if (aMaxBytes == null)
+      aMaxBytes = -1;
+
+    let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes);
+    let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes);
+
+    // Figure out which byte progress string to display
+    let transfer;
+    if (total < 0)
+      transfer = gStr.transferNoTotal;
+    else if (progressUnits == totalUnits)
+      transfer = gStr.transferSameUnits;
+    else
+      transfer = gStr.transferDiffUnits;
+
+    transfer = replaceInsert(transfer, 1, progress);
+    transfer = replaceInsert(transfer, 2, progressUnits);
+    transfer = replaceInsert(transfer, 3, total);
+    transfer = replaceInsert(transfer, 4, totalUnits);
+
+    return transfer;
+  },
+
+  /**
+   * Generate a "time left" string given an estimate on the time left and the
+   * last time. The extra time is used to give a better estimate on the time to
+   * show. Both the time values are doubles instead of integers to help get
+   * sub-second accuracy for current and future estimates.
+   *
+   * @param aSeconds
+   *        Current estimate on number of seconds left for the download
+   * @param [optional] aLastSec
+   *        Last time remaining in seconds or Infinity for unknown
+   * @return A pair: [time left text, new value of "last seconds"]
+   */
+  getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec)
+  {
+    if (aLastSec == null)
+      aLastSec = Infinity;
+
+    if (aSeconds < 0)
+      return [gStr.timeUnknown, aLastSec];
+
+    // Try to find a cached lastSec for the given second
+    aLastSec = gCachedLast.reduce(function(aResult, aItem)
+      aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec);
+
+    // Add the current second/lastSec pair unless we have too many
+    gCachedLast.push([aSeconds, aLastSec]);
+    if (gCachedLast.length > kCachedLastMaxSize)
+      gCachedLast.shift();
+
+    // Apply smoothing only if the new time isn't a huge change -- e.g., if the
+    // new time is more than half the previous time; this is useful for
+    // downloads that start/resume slowly
+    if (aSeconds > aLastSec / 2) {
+      // Apply hysteresis to favor downward over upward swings
+      // 30% of down and 10% of up (exponential smoothing)
+      let (diff = aSeconds - aLastSec) {
+        aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff;
+      }
+
+      // If the new time is similar, reuse something close to the last seconds,
+      // but subtract a little to provide forward progress
+      let diff = aSeconds - aLastSec;
+      let diffPct = diff / aLastSec * 100;
+      if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5)
+        aSeconds = aLastSec - (diff < 0 ? .4 : .2);
+    }
+
+    // Decide what text to show for the time
+    let timeLeft;
+    if (aSeconds < 4) {
+      // Be friendly in the last few seconds
+      timeLeft = gStr.timeFewSeconds;
+    } else {
+      // Convert the seconds into its two largest units to display
+      let [time1, unit1, time2, unit2] =
+        DownloadUtils.convertTimeUnits(aSeconds);
+
+      let pair1 = replaceInsert(gStr.timePair, 1, time1);
+      pair1 = replaceInsert(pair1, 2, unit1);
+      let pair2 = replaceInsert(gStr.timePair, 1, time2);
+      pair2 = replaceInsert(pair2, 2, unit2);
+
+      // Only show minutes for under 1 hour unless there's a few minutes left;
+      // or the second pair is 0.
+      if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) {
+        timeLeft = replaceInsert(gStr.timeLeftSingle, 1, pair1);
+      } else {
+        // We've got 2 pairs of times to display
+        timeLeft = replaceInsert(gStr.timeLeftDouble, 1, pair1);
+        timeLeft = replaceInsert(timeLeft, 2, pair2);
+      }
+    }
+
+    return [timeLeft, aSeconds];
+  },
+
+  /**
+   * Get the appropriate display host string for a URI string depending on if
+   * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
+   *
+   * @param aURIString
+   *        The URI string to try getting an eTLD + 1, etc.
+   * @return A pair: [display host for the URI string, full host name]
+   */
+  getURIHost: function DU_getURIHost(aURIString)
+  {
+    let ioService = Cc["@mozilla.org/network/io-service;1"].
+                    getService(Ci.nsIIOService);
+    let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
+                      getService(Ci.nsIEffectiveTLDService);
+    let idnService = Cc["@mozilla.org/network/idn-service;1"].
+                     getService(Ci.nsIIDNService);
+
+    // Get a URI that knows about its components
+    let uri = ioService.newURI(aURIString, null, null);
+
+    // Get the inner-most uri for schemes like jar:
+    if (uri instanceof Ci.nsINestedURI)
+      uri = uri.innermostURI;
+
+    let fullHost;
+    try {
+      // Get the full host name; some special URIs fail (data: jar:)
+      fullHost = uri.host;
+    } catch (e) {
+      fullHost = "";
+    }
+
+    let displayHost;
+    try {
+      // This might fail if it's an IP address or doesn't have more than 1 part
+      let baseDomain = eTLDService.getBaseDomain(uri);
+
+      // Convert base domain for display; ignore the isAscii out param
+      displayHost = idnService.convertToDisplayIDN(baseDomain, {});
+    } catch (e) {
+      // Default to the host name
+      displayHost = fullHost;
+    }
+
+    // Check if we need to show something else for the host
+    if (uri.scheme == "file") {
+      // Display special text for file protocol
+      displayHost = gStr.doneFileScheme;
+      fullHost = displayHost;
+    } else if (displayHost.length == 0) {
+      // Got nothing; show the scheme (data: about: moz-icon:)
+      displayHost = replaceInsert(gStr.doneScheme, 1, uri.scheme);
+      fullHost = displayHost;
+    } else if (uri.port != -1) {
+      // Tack on the port if it's not the default port
+      let port = ":" + uri.port;
+      displayHost += port;
+      fullHost += port;
+    }
+
+    return [displayHost, fullHost];
+  },
+
+  /**
+   * Converts a number of bytes to the appropriate unit that results in a
+   * number that needs fewer than 4 digits
+   *
+   * @param aBytes
+   *        Number of bytes to convert
+   * @return A pair: [new value with 3 sig. figs., its unit]
+   */
+  convertByteUnits: function DU_convertByteUnits(aBytes)
+  {
+    let unitIndex = 0;
+
+    // Convert to next unit if it needs 4 digits (after rounding), but only if
+    // we know the name of the next unit
+    while ((aBytes >= 999.5) && (unitIndex < gStr.units.length - 1)) {
+      aBytes /= 1024;
+      unitIndex++;
+    }
+
+    // Get rid of insignificant bits by truncating to 1 or 0 decimal points
+    // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
+    aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
+
+    return [aBytes, gStr.units[unitIndex]];
+  },
+
+  /**
+   * Converts a number of seconds to the two largest units. Time values are
+   * whole numbers, and units have the correct plural/singular form.
+   *
+   * @param aSecs
+   *        Seconds to convert into the appropriate 2 units
+   * @return 4-item array [first value, its unit, second value, its unit]
+   */
+  convertTimeUnits: function DU_convertTimeUnits(aSecs)
+  {
+    // These are the maximum values for seconds, minutes, hours corresponding
+    // with gStr.timeUnits without the last item
+    let timeSize = [60, 60, 24];
+
+    let time = aSecs;
+    let scale = 1;
+    let unitIndex = 0;
+
+    // Keep converting to the next unit while we have units left and the
+    // current one isn't the largest unit possible
+    while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) {
+      time /= timeSize[unitIndex];
+      scale *= timeSize[unitIndex];
+      unitIndex++;
+    }
+
+    let value = convertTimeUnitsValue(time);
+    let units = convertTimeUnitsUnits(value, unitIndex);
+
+    let extra = aSecs - value * scale;
+    let nextIndex = unitIndex - 1;
+
+    // Convert the extra time to the next largest unit
+    for (let index = 0; index < nextIndex; index++)
+      extra /= timeSize[index];
+
+    let value2 = convertTimeUnitsValue(extra);
+    let units2 = convertTimeUnitsUnits(value2, nextIndex);
+
+    return [value, units, value2, units2];
+  },
+};
+
+/**
+ * Private helper for convertTimeUnits that gets the display value of a time
+ *
+ * @param aTime
+ *        Time value for display
+ * @return An integer value for the time rounded down
+ */
+function convertTimeUnitsValue(aTime)
+{
+  return Math.floor(aTime);
+}
+
+/**
+ * Private helper for convertTimeUnits that gets the display units of a time
+ *
+ * @param aTime
+ *        Time value for display
+ * @param aIndex
+ *        Index into gStr.timeUnits for the appropriate unit
+ * @return The appropriate plural form of the unit for the time
+ */
+function convertTimeUnitsUnits(aTime, aIndex)
+{
+  // Negative index would be an invalid unit, so just give empty
+  if (aIndex < 0)
+    return "";
+
+  return PluralForm.get(aTime, gStr.timeUnits[aIndex]);
+}
+
+/**
+ * Private helper function to replace a placeholder string with a real string
+ *
+ * @param aText
+ *        Source text containing placeholder (e.g., #1)
+ * @param aIndex
+ *        Index number of placeholder to replace
+ * @param aValue
+ *        New string to put in place of placeholder
+ * @return The string with placeholder replaced with the new string
+ */
+function replaceInsert(aText, aIndex, aValue)
+{
+  return aText.replace("#" + aIndex, aValue);
+}
+
+/**
+ * Private helper function to log errors to the error console and command line
+ *
+ * @param aMsg
+ *        Error message to log or an array of strings to concat
+ */
+function log(aMsg)
+{
+  let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
+  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
+    logStringMessage(msg);
+  dump(msg + "\n");
+}
--- a/toolkit/mozapps/downloads/Makefile.in
+++ b/toolkit/mozapps/downloads/Makefile.in
@@ -30,23 +30,33 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
-DEPTH   = ../../..
+DEPTH     = ../../..
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
-VPATH   = @srcdir@
+VPATH     = @srcdir@
 
 include $(topsrcdir)/config/config.mk
 
-DIRS = src
+MODULE = helperAppDlg
+
+EXTRA_COMPONENTS = nsHelperAppDlg.js
+GARBAGE += nsHelperAppDlg.js
+
+EXTRA_JS_MODULES = \
+  DownloadLastDir.jsm \
+  DownloadUtils.jsm \
+  $(NULL)
+
 ifdef ENABLE_TESTS
 DIRS += tests
 endif
 
 include $(topsrcdir)/config/rules.mk
 
-
+nsHelperAppDlg.js: nsHelperAppDlg.js.in
+	$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $^ > $@ 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js.in
@@ -0,0 +1,1238 @@
+/*
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla.org Code.
+#
+# The Initial Developer of the Original Code is
+# Doron Rosenberg.
+# Portions created by the Initial Developer are Copyright (C) 2001
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Bill Law <law@netscape.com>
+#   Scott MacGregor <mscott@netscape.com>
+#   Ben Goodger <ben@bengoodger.com> (2.0)
+#   Fredrik Holmqvist <thesuckiestemail@yahoo.se>
+#   Dan Mosedale <dmose@mozilla.org>
+#   Jim Mathies <jmathies@mozilla.com>
+#   Ehsan Akhgari <ehsan.akhgari@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+//// Helper Functions
+
+/**
+ * Determines if a given directory is able to be used to download to.
+ *
+ * @param aDirectory
+ *        The directory to check.
+ * @returns true if we can use the directory, false otherwise.
+ */
+function isUsableDirectory(aDirectory)
+{
+  return aDirectory.exists() && aDirectory.isDirectory() &&
+         aDirectory.isWritable();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//// nsUnkownContentTypeDialog
+
+/* This file implements the nsIHelperAppLauncherDialog interface.
+ *
+ * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
+ * comprised of:
+ *   - a JS constructor function
+ *   - a prototype providing all the interface methods and implementation stuff
+ *
+ * In addition, this file implements an nsIModule object that registers the
+ * nsUnknownContentTypeDialog component.
+ */
+
+const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
+const nsITimer = Components.interfaces.nsITimer;
+
+Components.utils.import("resource://gre/modules/DownloadLastDir.jsm");
+
+/* ctor
+ */
+function nsUnknownContentTypeDialog() {
+    // Initialize data properties.
+    this.mLauncher = null;
+    this.mContext  = null;
+    this.mSourcePath = null;
+    this.chosenApp = null;
+    this.givenDefaultApp = false;
+    this.updateSelf = true;
+    this.mTitle    = "";
+}
+
+nsUnknownContentTypeDialog.prototype = {
+    nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
+
+    QueryInterface: function (iid) {
+        if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
+            !iid.equals(Components.interfaces.nsITimerCallback) &&
+            !iid.equals(Components.interfaces.nsISupports)) {
+            throw Components.results.NS_ERROR_NO_INTERFACE;
+        }
+        return this;
+    },
+
+    // ---------- nsIHelperAppLauncherDialog methods ----------
+
+    // show: Open XUL dialog using window watcher.  Since the dialog is not
+    //       modal, it needs to be a top level window and the way to open
+    //       one of those is via that route).
+    show: function(aLauncher, aContext, aReason)  {
+      this.mLauncher = aLauncher;
+      this.mContext  = aContext;
+
+      const nsITimer = Components.interfaces.nsITimer;
+      this._showTimer = Components.classes["@mozilla.org/timer;1"]
+                                  .createInstance(nsITimer);
+      this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
+    },
+
+    // When opening from new tab, if tab closes while dialog is opening,
+    // (which is a race condition on the XUL file being cached and the timer
+    // in nsExternalHelperAppService), the dialog gets a blur and doesn't
+    // activate the OK button.  So we wait a bit before doing opening it.
+    reallyShow: function() {
+        try {
+          var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+          var dwi = ir.getInterface(Components.interfaces.nsIDOMWindowInternal);
+          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+                             .getService(Components.interfaces.nsIWindowWatcher);
+          this.mDialog = ww.openWindow(dwi,
+                                       "chrome://mozapps/content/downloads/unknownContentType.xul",
+                                       null,
+                                       "chrome,centerscreen,titlebar,dialog=yes,dependent",
+                                       null);
+        } catch (ex) {
+          // The containing window may have gone away.  Break reference
+          // cycles and stop doing the download.
+          const NS_BINDING_ABORTED = 0x804b0002;
+          this.mLauncher.cancel(NS_BINDING_ABORTED);
+          return;
+        }
+
+        // Hook this object to the dialog.
+        this.mDialog.dialog = this;
+
+        // Hook up utility functions.
+        this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
+
+        // Watch for error notifications.
+        this.progressListener.helperAppDlg = this;
+        this.mLauncher.setWebProgressListener(this.progressListener);
+    },
+
+    // promptForSaveToFile:  Display file picker dialog and return selected file.
+    //                       This is called by the External Helper App Service
+    //                       after the ucth dialog calls |saveToDisk| with a null
+    //                       target filename (no target, therefore user must pick).
+    //
+    //                       Alternatively, if the user has selected to have all
+    //                       files download to a specific location, return that
+    //                       location and don't ask via the dialog. 
+    //
+    // Note - this function is called without a dialog, so it cannot access any part
+    // of the dialog XUL as other functions on this object do. 
+    promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
+      var result = null;
+      
+      this.mLauncher = aLauncher;
+
+      let prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefBranch);
+      let bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].
+        getService(Components.interfaces.nsIStringBundleService).
+        createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
+
+      if (!aForcePrompt) {
+        // Check to see if the user wishes to auto save to the default download
+        // folder without prompting. Note that preference might not be set.
+        let autodownload = false;
+        try {
+          autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
+        } catch (e) { }
+      
+        if (autodownload) {
+          // Retrieve the user's default download directory
+          let dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
+                                  .getService(Components.interfaces.nsIDownloadManager);
+          let defaultFolder = dnldMgr.userDownloadsDirectory;
+
+          try {
+            result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
+          }
+          catch (ex) {
+            if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) {
+              let prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
+                getService(Components.interfaces.nsIPromptService);
+
+              // Display error alert (using text supplied by back-end)
+              prompter.alert(this.dialog,
+                             bundle.GetStringFromName("badPermissions.title"),
+                             bundle.GetStringFromName("badPermissions"));
+
+              return;
+            }
+          }
+
+          // Check to make sure we have a valid directory, otherwise, prompt
+          if (result)
+            return result;
+        }
+      }
+      
+      // Use file picker to show dialog.
+      var nsIFilePicker = Components.interfaces.nsIFilePicker;
+      var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+      var windowTitle = bundle.GetStringFromName("saveDialogTitle");
+      var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal);
+      picker.init(parent, windowTitle, nsIFilePicker.modeSave);
+      picker.defaultString = aDefaultFile;
+
+      if (aSuggestedFileExtension) {
+        // aSuggestedFileExtension includes the period, so strip it
+        picker.defaultExtension = aSuggestedFileExtension.substring(1);
+      } 
+      else {
+        try {
+          picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
+        } 
+        catch (ex) { }
+      }
+
+      var wildCardExtension = "*";
+      if (aSuggestedFileExtension) {
+        wildCardExtension += aSuggestedFileExtension;
+        picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension);
+      }
+
+      picker.appendFilters( nsIFilePicker.filterAll );
+
+      // Default to lastDir if it is valid, otherwise use the user's default
+      // downloads directory.  userDownloadsDirectory should always return a
+      // valid directory, so we can safely default to it.
+      var dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
+                              .getService(Components.interfaces.nsIDownloadManager);
+      picker.displayDirectory = dnldMgr.userDownloadsDirectory;
+
+      // The last directory preference may not exist, which will throw.
+      try {
+        var lastDir = gDownloadLastDir.file;
+        if (isUsableDirectory(lastDir))
+          picker.displayDirectory = lastDir;
+      }
+      catch (ex) {
+      }
+
+      if (picker.show() == nsIFilePicker.returnCancel) {
+        // null result means user cancelled.
+        return null;
+      }
+
+      // Be sure to save the directory the user chose through the Save As... 
+      // dialog  as the new browser.download.dir since the old one
+      // didn't exist.
+      result = picker.file;
+
+      if (result) {
+        try {
+          // Remove the file so that it's not there when we ensure non-existence later;
+          // this is safe because for the file to exist, the user would have had to
+          // confirm that he wanted the file overwritten.
+          if (result.exists())
+            result.remove(false);
+        }
+        catch (e) { }
+        var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile);
+
+        // Do not store the last save directory as a pref inside the private browsing mode
+        gDownloadLastDir.file = newDir;
+
+        result = this.validateLeafName(newDir, result.leafName, null);
+      }
+      return result;
+    },
+
+    /**
+     * Ensures that a local folder/file combination does not already exist in
+     * the file system (or finds such a combination with a reasonably similar
+     * leaf name), creates the corresponding file, and returns it.
+     *
+     * @param   aLocalFile
+     *          the folder where the file resides
+     * @param   aLeafName
+     *          the string name of the file (may be empty if no name is known,
+     *          in which case a name will be chosen)
+     * @param   aFileExt
+     *          the extension of the file, if one is known; this will be ignored
+     *          if aLeafName is non-empty
+     * @returns nsILocalFile
+     *          the created file
+     */
+    validateLeafName: function (aLocalFile, aLeafName, aFileExt)
+    {
+      if (!(aLocalFile && isUsableDirectory(aLocalFile)))
+        return null;
+
+      // Remove any leading periods, since we don't want to save hidden files
+      // automatically.
+      aLeafName = aLeafName.replace(/^\.+/, "");
+
+      if (aLeafName == "")
+        aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
+      aLocalFile.append(aLeafName);
+
+      this.makeFileUnique(aLocalFile);
+
+#ifdef XP_WIN
+      let ext;
+      try {
+        // We can fail here if there's no primary extension set
+        ext = "." + this.mLauncher.MIMEInfo.primaryExtension;
+      } catch (e) { }
+
+      // Append a file extension if it's an executable that doesn't have one
+      // but make sure we actually have an extension to add
+      let leaf = aLocalFile.leafName;
+      if (aLocalFile.isExecutable() && ext &&
+          leaf.substring(leaf.length - ext.length) != ext) {
+        let f = aLocalFile.clone();
+        aLocalFile.leafName = leaf + ext;
+
+        f.remove(false);
+        this.makeFileUnique(aLocalFile);
+      }
+#endif
+
+      return aLocalFile;
+    },
+
+    /**
+     * Generates and returns a uniquely-named file from aLocalFile.  If
+     * aLocalFile does not exist, it will be the file returned; otherwise, a
+     * file whose name is similar to that of aLocalFile will be returned.
+     */
+    makeFileUnique: function (aLocalFile)
+    {
+      try {
+        // Note - this code is identical to that in 
+        //   toolkit/content/contentAreaUtils.js.
+        // If you are updating this code, update that code too! We can't share code
+        // here since this is called in a js component. 
+        var collisionCount = 0;
+        while (aLocalFile.exists()) {
+          collisionCount++;
+          if (collisionCount == 1) {
+            // Append "(2)" before the last dot in (or at the end of) the filename
+            // special case .ext.gz etc files so we don't wind up with .tar(2).gz
+            if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) {
+              aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
+            }
+            else {
+              aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
+            }
+          }
+          else {
+            // replace the last (n) in the filename with (n+1)
+            aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
+          }
+        }
+        aLocalFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
+      }
+      catch (e) {
+        dump("*** exception in validateLeafName: " + e + "\n");
+
+        if (e.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED)
+          throw e;
+
+        if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
+          aLocalFile.append("unnamed");
+          if (aLocalFile.exists())
+            aLocalFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
+        }
+      }
+    },
+    
+    // ---------- implementation methods ----------
+
+    // Web progress listener so we can detect errors while mLauncher is
+    // streaming the data to a temporary file.
+    progressListener: {
+        // Implementation properties.
+        helperAppDlg: null,
+
+        // nsIWebProgressListener methods.
+        // Look for error notifications and display alert to user.
+        onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
+            if ( aStatus != Components.results.NS_OK ) {
+                // Get prompt service.
+                var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
+                                   .getService( Components.interfaces.nsIPromptService );
+                // Display error alert (using text supplied by back-end).
+                prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
+
+                // Close the dialog.
+                this.helperAppDlg.onCancel();
+                if ( this.helperAppDlg.mDialog ) {
+                    this.helperAppDlg.mDialog.close();
+                }
+            }
+        },
+
+        // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
+        onProgressChange: function( aWebProgress,
+                                    aRequest,
+                                    aCurSelfProgress,
+                                    aMaxSelfProgress,
+                                    aCurTotalProgress,
+                                    aMaxTotalProgress ) {
+        },
+
+        onProgressChange64: function( aWebProgress,
+                                      aRequest,
+                                      aCurSelfProgress,
+                                      aMaxSelfProgress,
+                                      aCurTotalProgress,
+                                      aMaxTotalProgress ) {
+        },
+
+
+
+        onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
+        },
+
+        onLocationChange: function( aWebProgress, aRequest, aLocation ) {
+        },
+
+        onSecurityChange: function( aWebProgress, aRequest, state ) {
+        },
+
+        onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) {
+          return true;
+	}
+    },
+
+    // initDialog:  Fill various dialog fields with initial content.
+    initDialog : function() {
+      // Put file name in window title.
+      var suggestedFileName = this.mLauncher.suggestedFileName;
+
+      // Some URIs do not implement nsIURL, so we can't just QI.
+      var url   = this.mLauncher.source;
+      var fname = "";
+      this.mSourcePath = url.prePath;
+      try {
+          url = url.QueryInterface( Components.interfaces.nsIURL );
+          // A url, use file name from it.
+          fname = url.fileName;
+          this.mSourcePath += url.directory;
+      } catch (ex) {
+          // A generic uri, use path.
+          fname = url.path;
+          this.mSourcePath += url.path;
+      }
+
+      if (suggestedFileName)
+        fname = suggestedFileName;
+      
+      var displayName = fname.replace(/ +/g, " ");
+
+      this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
+      this.mDialog.document.title = this.mTitle;
+
+      // Put content type, filename and location into intro.
+      this.initIntro(url, fname, displayName);
+
+      var iconString = "moz-icon://" + fname + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
+      this.dialogElement("contentTypeImage").setAttribute("src", iconString);
+
+      // if always-save and is-executable and no-handler
+      // then set up simple ui
+      var mimeType = this.mLauncher.MIMEInfo.MIMEType;
+      var shouldntRememberChoice = (mimeType == "application/octet-stream" || 
+                                    mimeType == "application/x-msdownload" ||
+                                    this.mLauncher.targetFileIsExecutable);
+      if (shouldntRememberChoice && !this.openWithDefaultOK()) {
+        // hide featured choice 
+        this.dialogElement("normalBox").collapsed = true;
+        // show basic choice 
+        this.dialogElement("basicBox").collapsed = false;
+        // change button labels and icons; use "save" icon for the accept
+        // button since it's the only action possible
+        let acceptButton = this.mDialog.document.documentElement
+                               .getButton("accept");
+        acceptButton.label = this.dialogElement("strings")
+                                 .getString("unknownAccept.label");
+        acceptButton.setAttribute("icon", "save");
+        this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
+        // hide other handler
+        this.dialogElement("openHandler").collapsed = true;
+        // set save as the selected option
+        this.dialogElement("mode").selectedItem = this.dialogElement("save");
+      }
+      else {
+        this.initAppAndSaveToDiskValues();
+
+        // Initialize "always ask me" box. This should always be disabled
+        // and set to true for the ambiguous type application/octet-stream.
+        // We don't also check for application/x-msdownload here since we
+        // want users to be able to autodownload .exe files. 
+        var rememberChoice = this.dialogElement("rememberChoice");
+
+#if 0
+        // Just because we have a content-type of application/octet-stream 
+        // here doesn't actually mean that the content is of that type. Many 
+        // servers default to sending text/plain for file types they don't know
+        // about. To account for this, the uriloader does some checking to see 
+        // if a file sent as text/plain contains binary characters, and if so (*)
+        // it morphs the content-type into application/octet-stream so that
+        // the file can be properly handled. Since this is not generic binary
+        // data, rather, a data format that the system probably knows about, 
+        // we don't want to use the content-type provided by this dialog's 
+        // opener, as that's the generic application/octet-stream that the 
+        // uriloader has passed, rather we want to ask the MIME Service.
+        // This is so we don't needlessly disable the "autohandle" checkbox.
+        var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
+        var type = mimeService.getTypeFromURI(this.mLauncher.source);
+        this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, "");
+
+        if (type == "application/octet-stream") {
+#endif
+        if (shouldntRememberChoice) {
+          rememberChoice.checked = false;
+          rememberChoice.disabled = true;
+        }
+        else {
+          rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
+        }
+        this.toggleRememberChoice(rememberChoice);
+
+        // XXXben - menulist won't init properly, hack. 
+        var openHandler = this.dialogElement("openHandler");
+        openHandler.parentNode.removeChild(openHandler);
+        var openHandlerBox = this.dialogElement("openHandlerBox");
+        openHandlerBox.appendChild(openHandler);
+      }
+
+      this.mDialog.setTimeout("dialog.postShowCallback()", 0);
+      
+      this.mDialog.document.documentElement.getButton("accept").disabled = true;
+      this._showTimer = Components.classes["@mozilla.org/timer;1"]
+                                  .createInstance(nsITimer);
+      this._showTimer.initWithCallback(this, 250, nsITimer.TYPE_ONE_SHOT);
+    },
+
+    notify: function (aTimer) {
+      if (aTimer == this._showTimer) {
+        if (!this.mDialog) {
+          this.reallyShow();
+        } else {
+          // The user may have already canceled the dialog.
+          try {
+            if (!this._blurred) {
+              this.mDialog.document.documentElement.getButton("accept").disabled = false;
+            }
+          } catch (ex) {}
+          this._delayExpired = true;
+        }
+        // The timer won't release us, so we have to release it.
+        this._showTimer = null;
+      }
+      else if (aTimer == this._saveToDiskTimer) {
+        // Since saveToDisk may open a file picker and therefore block this routine,
+        // we should only call it once the dialog is closed.
+        this.mLauncher.saveToDisk(null, false);
+        this._saveToDiskTimer = null;
+      }
+    },
+
+    postShowCallback: function () {
+      this.mDialog.sizeToContent();
+
+      // Set initial focus
+      this.dialogElement("mode").focus();
+    },
+
+    // initIntro:
+    initIntro: function(url, filename, displayname) {
+        this.dialogElement( "location" ).value = displayname;
+        this.dialogElement( "location" ).setAttribute("realname", filename);
+        this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);
+
+        // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
+        // url...
+        var pathString = this.mSourcePath;
+        try 
+        {
+          var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
+          if (fileURL)
+          {
+            var fileObject = fileURL.file;
+            if (fileObject)
+            {
+              var parentObject = fileObject.parent;
+              if (parentObject)
+              {
+                pathString = parentObject.path;
+              }
+            }
+          }
+        } catch(ex) {}
+
+        if (pathString == this.mSourcePath)
+        {
+          // wasn't a fileURL
+          var tmpurl = url.clone(); // don't want to change the real url
+          try {
+            tmpurl.userPass = "";
+          } catch (ex) {}
+          pathString = tmpurl.prePath;
+        }
+
+        // Set the location text, which is separate from the intro text so it can be cropped
+        var location = this.dialogElement( "source" );
+        location.value = pathString;
+        location.setAttribute("tooltiptext", this.mSourcePath);
+        
+        // Show the type of file. 
+        var type = this.dialogElement("type");
+        var mimeInfo = this.mLauncher.MIMEInfo;
+        
+        // 1. Try to use the pretty description of the type, if one is available.
+        var typeString = mimeInfo.description;
+        
+        if (typeString == "") {
+          // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
+          var primaryExtension = "";
+          try {
+            primaryExtension = mimeInfo.primaryExtension;
+          }
+          catch (ex) {
+          }
+          if (primaryExtension != "")
+            typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]);
+          // 3. If we can't even do that, just give up and show the MIME type. 
+          else
+            typeString = mimeInfo.MIMEType;
+        }
+        
+        type.value = typeString;
+    },
+    
+    _blurred: false,
+    _delayExpired: false, 
+    onBlur: function(aEvent) {
+      this._blurred = true;
+      this.mDialog.document.documentElement.getButton("accept").disabled = true;
+    },
+    
+    onFocus: function(aEvent) {
+      this._blurred = false;
+      if (this._delayExpired) {
+        var script = "document.documentElement.getButton('accept').disabled = false";
+        this.mDialog.setTimeout(script, 250);
+      }
+    },
+
+    // Returns true if opening the default application makes sense.
+    openWithDefaultOK: function() {
+        // The checking is different on Windows...
+#ifdef XP_WIN
+        // Windows presents some special cases.
+        // We need to prevent use of "system default" when the file is
+        // executable (so the user doesn't launch nasty programs downloaded
+        // from the web), and, enable use of "system default" if it isn't
+        // executable (because we will prompt the user for the default app
+        // in that case).
+        
+        //  Default is Ok if the file isn't executable (and vice-versa).
+        return !this.mLauncher.targetFileIsExecutable;
+#else
+            // On other platforms, default is Ok if there is a default app.
+            // Note that nsIMIMEInfo providers need to ensure that this holds true
+            // on each platform.
+        return this.mLauncher.MIMEInfo.hasDefaultHandler;
+#endif
+    },
+    
+    // Set "default" application description field.
+    initDefaultApp: function() {
+      // Use description, if we can get one.
+      var desc = this.mLauncher.MIMEInfo.defaultDescription;
+      if (desc) {
+        var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
+        this.dialogElement("defaultHandler").label = defaultApp;
+      }
+      else {
+        this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
+        // Hide the default handler item too, in case the user picks a 
+        // custom handler at a later date which triggers the menulist to show.
+        this.dialogElement("defaultHandler").hidden = true;
+      }
+    },
+
+    // getPath:
+    getPath: function (aFile) {
+#ifdef XP_MACOSX
+      return aFile.leafName || aFile.path;
+#else
+      return aFile.path;
+#endif
+    },
+
+    // initAppAndSaveToDiskValues:
+    initAppAndSaveToDiskValues: function() {
+      var modeGroup = this.dialogElement("mode");
+
+      // We don't let users open .exe files or random binary data directly 
+      // from the browser at the moment because of security concerns. 
+      var openWithDefaultOK = this.openWithDefaultOK();
+      var mimeType = this.mLauncher.MIMEInfo.MIMEType;
+      if (this.mLauncher.targetFileIsExecutable || (
+          (mimeType == "application/octet-stream" ||
+           mimeType == "application/x-msdownload") && 
+           !openWithDefaultOK)) {
+        this.dialogElement("open").disabled = true;
+        var openHandler = this.dialogElement("openHandler");
+        openHandler.disabled = true;
+        openHandler.selectedItem = null;
+        modeGroup.selectedItem = this.dialogElement("save");
+        return;
+      }
+    
+      // Fill in helper app info, if there is any.
+      try {
+        this.chosenApp =
+          this.mLauncher.MIMEInfo.preferredApplicationHandler
+              .QueryInterface(Components.interfaces.nsILocalHandlerApp);
+      } catch (e) {
+        this.chosenApp = null;
+      }
+      // Initialize "default application" field.
+      this.initDefaultApp();
+
+      var otherHandler = this.dialogElement("otherHandler");
+              
+      // Fill application name textbox.
+      if (this.chosenApp && this.chosenApp.executable && 
+          this.chosenApp.executable.path) {
+        otherHandler.setAttribute("path",
+                                  this.getPath(this.chosenApp.executable));
+
+#if XP_MACOSX
+        this.chosenApp.executable.QueryInterface(Components.interfaces.nsILocalFileMac);
+        otherHandler.label = this.chosenApp.executable.bundleDisplayName;
+#else
+        otherHandler.label = this.chosenApp.executable.leafName;
+#endif
+        otherHandler.hidden = false;
+      }
+
+      var useDefault = this.dialogElement("useSystemDefault");
+      var openHandler = this.dialogElement("openHandler");
+      openHandler.selectedIndex = 0;
+
+      if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
+        // Open (using system default).
+        modeGroup.selectedItem = this.dialogElement("open");
+      } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
+        // Open with given helper app.
+        modeGroup.selectedItem = this.dialogElement("open");
+        openHandler.selectedIndex = 1;
+      } else {
+        // Save to disk.
+        modeGroup.selectedItem = this.dialogElement("save");
+      }
+      
+      // If we don't have a "default app" then disable that choice.
+      if (!openWithDefaultOK) {
+        var useDefault = this.dialogElement("defaultHandler");
+        var isSelected = useDefault.selected;
+        
+        // Disable that choice.
+        useDefault.hidden = true;
+        // If that's the default, then switch to "save to disk."
+        if (isSelected) {
+          openHandler.selectedIndex = 1;
+          modeGroup.selectedItem = this.dialogElement("save");
+        }
+      }
+      
+      otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
+      this.updateOKButton();
+    },
+
+    // Returns the user-selected application
+    helperAppChoice: function() {
+      return this.chosenApp;
+    },
+    
+    get saveToDisk() {
+      return this.dialogElement("save").selected;
+    },
+    
+    get useOtherHandler() {
+      return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
+    },
+    
+    get useSystemDefault() {
+      return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
+    },
+    
+    toggleRememberChoice: function (aCheckbox) {
+        this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
+        this.mDialog.sizeToContent();
+    },
+    
+    openHandlerCommand: function () {
+      var openHandler = this.dialogElement("openHandler");
+      if (openHandler.selectedItem.id == "choose")
+        this.chooseApp();
+      else
+        openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
+    },
+
+    updateOKButton: function() {
+      var ok = false;
+      if (this.dialogElement("save").selected) {
+        // This is always OK.
+        ok = true;
+      } 
+      else if (this.dialogElement("open").selected) {
+        switch (this.dialogElement("openHandler").selectedIndex) {
+        case 0:
+          // No app need be specified in this case.
+          ok = true;
+          break;
+        case 1:
+          // only enable the OK button if we have a default app to use or if 
+          // the user chose an app....
+          ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
+        break;
+        }
+      }
+
+      // Enable Ok button if ok to press.
+      this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
+    },
+    
+    // Returns true iff the user-specified helper app has been modified.
+    appChanged: function() {
+      return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
+    },
+
+    updateMIMEInfo: function() {
+      var needUpdate = false;
+      // If current selection differs from what's in the mime info object,
+      // then we need to update.
+      if (this.saveToDisk) {
+        needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
+        if (needUpdate)
+          this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
+      } 
+      else if (this.useSystemDefault) {
+        needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
+        if (needUpdate)
+          this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
+      } 
+      else {
+        // For "open with", we need to check both preferred action and whether the user chose
+        // a new app.
+        needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
+        if (needUpdate) {
+          this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
+          // App may have changed - Update application
+          var app = this.helperAppChoice();
+          this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
+        }
+      }
+      // We will also need to update if the "always ask" flag has changed.
+      needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
+
+      // One last special case: If the input "always ask" flag was false, then we always
+      // update.  In that case we are displaying the helper app dialog for the first
+      // time for this mime type and we need to store the user's action in the mimeTypes.rdf
+      // data source (whether that action has changed or not; if it didn't change, then we need
+      // to store the "always ask" flag so the helper app dialog will or won't display
+      // next time, per the user's selection).
+      needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
+
+      // Make sure mime info has updated setting for the "always ask" flag.
+      this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
+
+      return needUpdate;        
+    },
+    
+    // See if the user changed things, and if so, update the
+    // mimeTypes.rdf entry for this mime type.
+    updateHelperAppPref: function() {
+      var ha = new this.mDialog.HelperApps();
+      ha.updateTypeInfo(this.mLauncher.MIMEInfo);
+      ha.destroy();
+    },
+    
+    // onOK:
+    onOK: function() {
+      // Verify typed app path, if necessary.
+      if (this.useOtherHandler) {
+        var helperApp = this.helperAppChoice();
+        if (!helperApp || !helperApp.executable ||
+            !helperApp.executable.exists()) {
+          // Show alert and try again.        
+          var bundle = this.dialogElement("strings");                    
+          var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]);
+          var svc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
+          svc.alert(this.mDialog, bundle.getString("badApp.title"), msg);
+
+          // Disable the OK button.
+          this.mDialog.document.documentElement.getButton("accept").disabled = true;
+          this.dialogElement("mode").focus();          
+
+          // Clear chosen application.
+          this.chosenApp = null;
+
+          // Leave dialog up.
+          return false;
+        }
+      }
+        
+      // Remove our web progress listener (a progress dialog will be
+      // taking over).
+      this.mLauncher.setWebProgressListener(null);
+
+      // saveToDisk and launchWithApplication can return errors in 
+      // certain circumstances (e.g. The user clicks cancel in the
+      // "Save to Disk" dialog. In those cases, we don't want to
+      // update the helper application preferences in the RDF file.
+      try {
+        var needUpdate = this.updateMIMEInfo();
+        
+        if (this.dialogElement("save").selected) {
+          // If we're using a default download location, create a path
+          // for the file to be saved to to pass to |saveToDisk| - otherwise
+          // we must ask the user to pick a save name.
+
+#if 0
+          var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
+          var targetFile = null;
+          try {
+            targetFile = prefs.getComplexValue("browser.download.defaultFolder", 
+                                               Components.interfaces.nsILocalFile);
+            var leafName = this.dialogElement("location").getAttribute("realname");
+            // Ensure that we don't overwrite any existing files here. 
+            targetFile = this.validateLeafName(targetFile, leafName, null);
+          }
+          catch(e) { }
+
+          this.mLauncher.saveToDisk(targetFile, false);
+#endif
+
+          // see @notify
+          // we cannot use opener's setTimeout, see bug 420405
+          this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"]
+                                            .createInstance(nsITimer);
+          this._saveToDiskTimer.initWithCallback(this, 0,
+                                                 nsITimer.TYPE_ONE_SHOT);
+        }
+        else
+          this.mLauncher.launchWithApplication(null, false);
+
+        // Update user pref for this mime type (if necessary). We do not
+        // store anything in the mime type preferences for the ambiguous
+        // type application/octet-stream. We do NOT do this for 
+        // application/x-msdownload since we want users to be able to 
+        // autodownload these to disk. 
+        if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
+          this.updateHelperAppPref();
+      } catch(e) { }
+
+      // Unhook dialog from this object.
+      this.mDialog.dialog = null;
+
+      // Close up dialog by returning true.
+      return true;
+    },
+
+    // onCancel:
+    onCancel: function() {
+      // Remove our web progress listener.
+      this.mLauncher.setWebProgressListener(null);
+
+      // Cancel app launcher.
+      try {
+        const NS_BINDING_ABORTED = 0x804b0002;
+        this.mLauncher.cancel(NS_BINDING_ABORTED);
+      } catch(exception) {
+      }
+
+      // Unhook dialog from this object.
+      this.mDialog.dialog = null;
+
+      // Close up dialog by returning true.
+      return true;
+    },
+
+    // dialogElement:  Convenience. 
+    dialogElement: function(id) {
+      return this.mDialog.document.getElementById(id);
+    },
+
+    // Retrieve the pretty description from the file
+    getFileDisplayName: function getFileDisplayName(file)
+    { 
+#ifdef XP_WIN
+        if (file instanceof Components.interfaces.nsILocalFileWin) {
+          try {
+            return file.getVersionInfoField("FileDescription");
+          } catch (ex) {
+          }
+        }
+#endif
+        return file.leafName;
+    },
+
+    // chooseApp:  Open file picker and prompt user for application.
+    chooseApp: function() {
+#ifdef XP_WIN
+    // Protect against the lack of an extension    
+    var fileExtension = "";
+    try {
+        fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
+    } catch(ex) {
+    }
+
+    // Try to use the pretty description of the type, if one is available.
+    var typeString = this.mLauncher.MIMEInfo.description;
+
+    if (!typeString) {
+      // If there is none, use the extension to 
+      // identify the file, e.g. "ZIP file"
+      if (fileExtension) {
+        typeString =
+          this.dialogElement("strings").
+          getFormattedString("fileType", [fileExtension.toUpperCase()]);
+      } else {
+        // If we can't even do that, just give up and show the MIME type.
+        typeString = this.mLauncher.MIMEInfo.MIMEType;
+      }
+    }
+
+    var params = {};
+    params.title = 
+      this.dialogElement("strings").getString("chooseAppFilePickerTitle");
+    params.description = typeString;
+    params.filename    = this.mLauncher.suggestedFileName;
+    params.mimeInfo    = this.mLauncher.MIMEInfo;
+    params.handlerApp  = null;
+
+    this.mDialog.openDialog("chrome://global/content/appPicker.xul", null,
+                            "chrome,modal,centerscreen,titlebar,dialog=yes",
+                            params);
+
+    if (params.handlerApp &&
+        params.handlerApp.executable &&
+        params.handlerApp.executable.isFile()) {
+        // Show the "handler" menulist since we have a (user-specified) 
+        // application now.
+        this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
+
+        // Remember the file they chose to run.
+        this.chosenApp = params.handlerApp;
+
+        // Update dialog
+        var otherHandler = this.dialogElement("otherHandler");
+        otherHandler.removeAttribute("hidden");
+        otherHandler.setAttribute("path",
+          this.getPath(this.chosenApp.executable));
+        otherHandler.label = 
+          this.getFileDisplayName(this.chosenApp.executable);
+        this.dialogElement("openHandler").selectedIndex = 1;
+        this.dialogElement("openHandler").setAttribute("lastSelectedItemID",
+          "otherHandler");
+        this.dialogElement("mode").selectedItem = this.dialogElement("open");
+    } else {
+        var openHandler = this.dialogElement("openHandler");
+        var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
+        if (!lastSelectedID)
+            lastSelectedID = "defaultHandler";
+        openHandler.selectedItem = this.dialogElement(lastSelectedID);
+    }
+
+#else
+      var nsIFilePicker = Components.interfaces.nsIFilePicker;
+      var fp = Components.classes["@mozilla.org/filepicker;1"]
+                         .createInstance(nsIFilePicker);
+      fp.init(this.mDialog,
+              this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
+              nsIFilePicker.modeOpen);
+
+      fp.appendFilters(nsIFilePicker.filterApps);
+
+      if (fp.show() == nsIFilePicker.returnOK && fp.file) {
+        // Show the "handler" menulist since we have a (user-specified) 
+        // application now.
+        this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
+        
+        // Remember the file they chose to run.
+        var localHandlerApp = 
+          Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
+          createInstance(Components.interfaces.nsILocalHandlerApp);
+        localHandlerApp.executable = fp.file;
+        this.chosenApp = localHandlerApp;
+        
+        // Update dialog.
+        var otherHandler = this.dialogElement("otherHandler");
+        otherHandler.removeAttribute("hidden");
+        otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
+#ifdef XP_MACOSX
+        this.chosenApp.executable
+            .QueryInterface(Components.interfaces.nsILocalFileMac);
+        otherHandler.label = this.chosenApp.executable.bundleDisplayName;
+#else
+        otherHandler.label = this.chosenApp.executable.leafName;
+#endif
+        this.dialogElement("openHandler").selectedIndex = 1;
+        this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
+        
+        this.dialogElement("mode").selectedItem = this.dialogElement("open");
+      }
+      else {
+        var openHandler = this.dialogElement("openHandler");
+        var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
+        if (!lastSelectedID)
+          lastSelectedID = "defaultHandler";
+        openHandler.selectedItem = this.dialogElement(lastSelectedID);
+      }
+#endif
+    },
+
+    // Turn this on to get debugging messages.
+    debug: false,
+
+    // Dump text (if debug is on).
+    dump: function( text ) {
+        if ( this.debug ) {
+            dump( text ); 
+        }
+    },
+
+    // dumpInfo:
+    doDebug: function() {
+        const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
+        // Open new progress dialog.
+        var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
+                         .createInstance( nsIProgressDialog );
+        // Show it.
+        progress.open( this.mDialog );
+    },
+
+    // dumpObj:
+    dumpObj: function( spec ) {
+         var val = "<undefined>";
+         try {
+             val = eval( "this."+spec ).toString();
+         } catch( exception ) {
+         }
+         this.dump( spec + "=" + val + "\n" );
+    },
+
+    // dumpObjectProperties
+    dumpObjectProperties: function( desc, obj ) {
+         for( prop in obj ) {
+             this.dump( desc + "." + prop + "=" );
+             var val = "<undefined>";
+             try {
+                 val = obj[ prop ];
+             } catch ( exception ) {
+             }
+             this.dump( val + "\n" );
+         }
+    }
+}
+
+// This Component's module implementation.  All the code below is used to get this
+// component registered and accessible via XPCOM.
+var module = {
+    // registerSelf: Register this component.
+    registerSelf: function (compMgr, fileSpec, location, type) {
+        compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+
+        compMgr.registerFactoryLocation( this.cid,
+                                         "Unknown Content Type Dialog",
+                                         this.contractId,
+                                         fileSpec,
+                                         location,
+                                         type );
+    },
+
+    // getClassObject: Return this component's factory object.
+    getClassObject: function (compMgr, cid, iid) {
+        if (!cid.equals(this.cid)) {
+            throw Components.results.NS_ERROR_NO_INTERFACE;
+        }
+
+        if (!iid.equals(Components.interfaces.nsIFactory)) {
+            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        }
+
+        return this.factory;
+    },
+
+    /* CID for this class */
+    cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
+
+    /* Contract ID for this class */
+    contractId: "@mozilla.org/helperapplauncherdialog;1",
+
+    /* factory object */
+    factory: {
+        // createInstance: Return a new nsProgressDialog object.
+        createInstance: function (outer, iid) {
+            if (outer != null)
+                throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+            return (new nsUnknownContentTypeDialog()).QueryInterface(iid);
+        }
+    },
+
+    // canUnload: n/a (returns true)
+    canUnload: function(compMgr) {
+        return true;
+    }
+};
+
+// NSGetModule: Return the nsIModule object.
+function NSGetModule(compMgr, fileSpec) {
+    return module;
+}
deleted file mode 100644
--- a/toolkit/mozapps/downloads/src/DownloadLastDir.jsm
+++ /dev/null
@@ -1,131 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Download Manager Utility Code.
- *
- * The Initial Developer of the Original Code is
- * Ehsan Akhgari <ehsan.akhgari@gmail.com>.
- * Portions created by the Initial Developer are Copyright (C) 2008
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-/*
- * The behavior implemented by gDownloadLastDir is documented here.
- *
- * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir
- * preference to store the last used download directory. The first time the user
- * switches into the private browsing mode, the last download directory is
- * preserved to the pref value, but if the user switches to another directory
- * during the private browsing mode, that directory is not stored in the pref,
- * and will be merely kept in memory.  When leaving the private browsing mode,
- * this in-memory value will be discarded, and the last download directory
- * will be reverted to the pref value.
- *
- * Both the pref and the in-memory value will be cleared when clearing the
- * browsing history.  This effectively changes the last download directory
- * to the default download directory on each platform.
- */
-
-const LAST_DIR_PREF = "browser.download.lastDir";
-const PBSVC_CID = "@mozilla.org/privatebrowsing;1";
-const nsILocalFile = Components.interfaces.nsILocalFile;
-
-var EXPORTED_SYMBOLS = [ "gDownloadLastDir" ];
-
-let pbSvc = null;
-if (PBSVC_CID in Components.classes) {
-  pbSvc = Components.classes[PBSVC_CID]
-                    .getService(Components.interfaces.nsIPrivateBrowsingService);
-}
-let prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
-                        .getService(Components.interfaces.nsIPrefBranch);
-
-let observer = {
-  QueryInterface: function (aIID) {
-    if (aIID.equals(Components.interfaces.nsIObserver) ||
-        aIID.equals(Components.interfaces.nsISupports) ||
-        aIID.equals(Components.interfaces.nsISupportsWeakReference))
-      return this;
-    throw Components.results.NS_NOINTERFACE;
-  },
-  observe: function (aSubject, aTopic, aData) {
-    switch (aTopic) {
-      case "private-browsing":
-        if (aData == "enter")
-          gDownloadLastDirFile = readLastDirPref();
-        else if (aData == "exit")
-          gDownloadLastDirFile = null;
-        break;
-      case "browser:purge-session-history":
-        gDownloadLastDirFile = null;
-        if (prefSvc.prefHasUserValue(LAST_DIR_PREF))
-          prefSvc.clearUserPref(LAST_DIR_PREF);
-        break;
-    }
-  }
-};
-
-let os = Components.classes["@mozilla.org/observer-service;1"]
-                   .getService(Components.interfaces.nsIObserverService);
-os.addObserver(observer, "private-browsing", true);
-os.addObserver(observer, "browser:purge-session-history", true);
-
-function readLastDirPref() {
-  try {
-    return prefSvc.getComplexValue(LAST_DIR_PREF, nsILocalFile);
-  }
-  catch (e) {
-    return null;
-  }
-}
-
-let gDownloadLastDirFile = readLastDirPref();
-let gDownloadLastDir = {
-  get file() {
-    if (gDownloadLastDirFile && !gDownloadLastDirFile.exists())
-      gDownloadLastDirFile = null;
-
-    if (pbSvc && pbSvc.privateBrowsingEnabled)
-      return gDownloadLastDirFile;
-    else
-      return readLastDirPref();
-  },
-  set file(val) {
-    if (pbSvc && pbSvc.privateBrowsingEnabled) {
-      if (val instanceof Components.interfaces.nsIFile)
-        gDownloadLastDirFile = val.clone();
-      else
-        gDownloadLastDirFile = null;
-    } else {
-      if (val instanceof Components.interfaces.nsIFile)
-        prefSvc.setComplexValue(LAST_DIR_PREF, nsILocalFile, val);
-      else if (prefSvc.prefHasUserValue(LAST_DIR_PREF))
-        prefSvc.clearUserPref(LAST_DIR_PREF);
-    }
-  }
-};
deleted file mode 100644
--- a/toolkit/mozapps/downloads/src/DownloadUtils.jsm
+++ /dev/null
@@ -1,508 +0,0 @@
-/* ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Download Manager Utility Code.
- *
- * The Initial Developer of the Original Code is
- * Edward Lee <edward.lee@engineering.uiuc.edu>.
- * Portions created by the Initial Developer are Copyright (C) 2008
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
-var EXPORTED_SYMBOLS = [ "DownloadUtils" ];
-
-/**
- * This module provides the DownloadUtils object which contains useful methods
- * for downloads such as displaying file sizes, transfer times, and download
- * locations.
- *
- * List of methods:
- *
- * [string status, double newLast]
- * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes,
- *                   [optional] double aSpeed, [optional] double aLastSec)
- *
- * string progress
- * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes)
- *
- * [string timeLeft, double newLast]
- * getTimeLeft(double aSeconds, [optional] double aLastSec)
- *
- * [string displayHost, string fullHost]
- * getURIHost(string aURIString)
- *
- * [double convertedBytes, string units]
- * convertByteUnits(int aBytes)
- *
- * [int time, string units, int subTime, string subUnits]
- * convertTimeUnits(double aSecs)
- */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-__defineGetter__("PluralForm", function() {
-  delete this.PluralForm;
-  Cu.import("resource://gre/modules/PluralForm.jsm");
-  return PluralForm;
-});
-
-const kDownloadProperties =
-  "chrome://mozapps/locale/downloads/downloads.properties";
-
-// These strings will be converted to the corresponding ones from the string
-// bundle on use
-let kStrings = {
-  statusFormat: "statusFormat2",
-  transferSameUnits: "transferSameUnits",
-  transferDiffUnits: "transferDiffUnits",
-  transferNoTotal: "transferNoTotal",
-  timePair: "timePair",
-  timeLeftSingle: "timeLeftSingle",
-  timeLeftDouble: "timeLeftDouble",
-  timeFewSeconds: "timeFewSeconds",
-  timeUnknown: "timeUnknown",
-  doneScheme: "doneScheme",
-  doneFileScheme: "doneFileScheme",
-  units: ["bytes", "kilobyte", "megabyte", "gigabyte"],
-  // Update timeSize in convertTimeUnits if changing the length of this array
-  timeUnits: ["seconds", "minutes", "hours", "days"],
-};
-
-// This object will lazily load the strings defined in kStrings
-let gStr = {
-  /**
-   * Initialize lazy string getters
-   */
-  _init: function()
-  {
-    // Make each "name" a lazy-loading string that knows how to load itself. We
-    // need to locally scope name and value to keep them around for the getter.
-    for (let [name, value] in Iterator(kStrings))
-      let ([n, v] = [name, value])
-        gStr.__defineGetter__(n, function() gStr._getStr(n, v));
-  },
-
-  /**
-   * Convert strings to those in the string bundle. This lazily loads the
-   * string bundle *once* only when used the first time.
-   */
-  get _getStr()
-  {
-    // Delete the getter to be overwritten
-    delete gStr._getStr;
-
-    // Lazily load the bundle into the closure on first call to _getStr
-    let getStr = Cc["@mozilla.org/intl/stringbundle;1"].
-                 getService(Ci.nsIStringBundleService).
-                 createBundle(kDownloadProperties).
-                 GetStringFromName;
-
-    // _getStr is a function that sets string "name" to stringbundle's "value"
-    return gStr._getStr = function(name, value) {
-      // Delete the getter to be overwritten
-      delete gStr[name];
-
-      try {
-        // "name" is a string or array of the stringbundle-loaded "value"
-        return gStr[name] = typeof value == "string" ?
-                            getStr(value) :
-                            value.map(getStr);
-      } catch (e) {
-        log(["Couldn't get string '", name, "' from property '", value, "'"]);
-        // Don't return anything (undefined), and because we deleted ourselves,
-        // future accesses will also be undefined
-      }
-    };
-  },
-};
-// Initialize the lazy string getters!
-gStr._init();
-
-// Keep track of at most this many second/lastSec pairs so that multiple calls
-// to getTimeLeft produce the same time left
-const kCachedLastMaxSize = 10;
-let gCachedLast = [];
-
-let DownloadUtils = {
-  /**
-   * Generate a full status string for a download given its current progress,
-   * total size, speed, last time remaining
-   *
-   * @param aCurrBytes
-   *        Number of bytes transferred so far
-   * @param [optional] aMaxBytes
-   *        Total number of bytes or -1 for unknown
-   * @param [optional] aSpeed
-   *        Current transfer rate in bytes/sec or -1 for unknown
-   * @param [optional] aLastSec
-   *        Last time remaining in seconds or Infinity for unknown
-   * @return A pair: [download status text, new value of "last seconds"]
-   */
-  getDownloadStatus: function DU_getDownloadStatus(aCurrBytes, aMaxBytes,
-                                                   aSpeed, aLastSec)
-  {
-    if (aMaxBytes == null)
-      aMaxBytes = -1;
-    if (aSpeed == null)
-      aSpeed = -1;
-    if (aLastSec == null)
-      aLastSec = Infinity;
-
-    // Calculate the time remaining if we have valid values
-    let seconds = (aSpeed > 0) && (aMaxBytes > 0) ?
-      (aMaxBytes - aCurrBytes) / aSpeed : -1;
-
-    // Update the bytes transferred and bytes total
-    let status;
-    let (transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes)) {
-      // Insert 1 is the download progress
-      status = replaceInsert(gStr.statusFormat, 1, transfer);
-    }
-
-    // Update the download rate
-    let ([rate, unit] = DownloadUtils.convertByteUnits(aSpeed)) {
-      // Insert 2 is the download rate
-      status = replaceInsert(status, 2, rate);
-      // Insert 3 is the |unit|/sec
-      status = replaceInsert(status, 3, unit);
-    }
-
-    // Update time remaining
-    let ([timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec)) {
-      // Insert 4 is the time remaining
-      status = replaceInsert(status, 4, timeLeft);
-
-      return [status, newLast];
-    }
-  },
-
-  /**
-   * Generate the transfer progress string to show the current and total byte
-   * size. Byte units will be as large as possible and the same units for
-   * current and max will be supressed for the former.
-   *
-   * @param aCurrBytes
-   *        Number of bytes transferred so far
-   * @param [optional] aMaxBytes
-   *        Total number of bytes or -1 for unknown
-   * @return The transfer progress text
-   */
-  getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes)
-  {
-    if (aMaxBytes == null)
-      aMaxBytes = -1;
-
-    let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes);
-    let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes);
-
-    // Figure out which byte progress string to display
-    let transfer;
-    if (total < 0)
-      transfer = gStr.transferNoTotal;
-    else if (progressUnits == totalUnits)
-      transfer = gStr.transferSameUnits;
-    else
-      transfer = gStr.transferDiffUnits;
-
-    transfer = replaceInsert(transfer, 1, progress);
-    transfer = replaceInsert(transfer, 2, progressUnits);
-    transfer = replaceInsert(transfer, 3, total);
-    transfer = replaceInsert(transfer, 4, totalUnits);
-
-    return transfer;
-  },
-
-  /**
-   * Generate a "time left" string given an estimate on the time left and the
-   * last time. The extra time is used to give a better estimate on the time to
-   * show. Both the time values are doubles instead of integers to help get
-   * sub-second accuracy for current and future estimates.
-   *
-   * @param aSeconds
-   *        Current estimate on number of seconds left for the download
-   * @param [optional] aLastSec
-   *        Last time remaining in seconds or Infinity for unknown
-   * @return A pair: [time left text, new value of "last seconds"]
-   */
-  getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec)
-  {
-    if (aLastSec == null)
-      aLastSec = Infinity;
-
-    if (aSeconds < 0)
-      return [gStr.timeUnknown, aLastSec];
-
-    // Try to find a cached lastSec for the given second
-    aLastSec = gCachedLast.reduce(function(aResult, aItem)
-      aItem[0] == aSeconds ? aItem[1] : aResult, aLastSec);
-
-    // Add the current second/lastSec pair unless we have too many
-    gCachedLast.push([aSeconds, aLastSec]);
-    if (gCachedLast.length > kCachedLastMaxSize)
-      gCachedLast.shift();
-
-    // Apply smoothing only if the new time isn't a huge change -- e.g., if the
-    // new time is more than half the previous time; this is useful for
-    // downloads that start/resume slowly
-    if (aSeconds > aLastSec / 2) {
-      // Apply hysteresis to favor downward over upward swings
-      // 30% of down and 10% of up (exponential smoothing)
-      let (diff = aSeconds - aLastSec) {
-        aSeconds = aLastSec + (diff < 0 ? .3 : .1) * diff;
-      }
-
-      // If the new time is similar, reuse something close to the last seconds,
-      // but subtract a little to provide forward progress
-      let diff = aSeconds - aLastSec;
-      let diffPct = diff / aLastSec * 100;
-      if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5)
-        aSeconds = aLastSec - (diff < 0 ? .4 : .2);
-    }
-
-    // Decide what text to show for the time
-    let timeLeft;
-    if (aSeconds < 4) {
-      // Be friendly in the last few seconds
-      timeLeft = gStr.timeFewSeconds;
-    } else {
-      // Convert the seconds into its two largest units to display
-      let [time1, unit1, time2, unit2] =
-        DownloadUtils.convertTimeUnits(aSeconds);
-
-      let pair1 = replaceInsert(gStr.timePair, 1, time1);
-      pair1 = replaceInsert(pair1, 2, unit1);
-      let pair2 = replaceInsert(gStr.timePair, 1, time2);
-      pair2 = replaceInsert(pair2, 2, unit2);
-
-      // Only show minutes for under 1 hour unless there's a few minutes left;
-      // or the second pair is 0.
-      if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) {
-        timeLeft = replaceInsert(gStr.timeLeftSingle, 1, pair1);
-      } else {
-        // We've got 2 pairs of times to display
-        timeLeft = replaceInsert(gStr.timeLeftDouble, 1, pair1);
-        timeLeft = replaceInsert(timeLeft, 2, pair2);
-      }
-    }
-
-    return [timeLeft, aSeconds];
-  },
-
-  /**
-   * Get the appropriate display host string for a URI string depending on if
-   * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
-   *
-   * @param aURIString
-   *        The URI string to try getting an eTLD + 1, etc.
-   * @return A pair: [display host for the URI string, full host name]
-   */
-  getURIHost: function DU_getURIHost(aURIString)
-  {
-    let ioService = Cc["@mozilla.org/network/io-service;1"].
-                    getService(Ci.nsIIOService);
-    let eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"].
-                      getService(Ci.nsIEffectiveTLDService);
-    let idnService = Cc["@mozilla.org/network/idn-service;1"].
-                     getService(Ci.nsIIDNService);
-
-    // Get a URI that knows about its components
-    let uri = ioService.newURI(aURIString, null, null);
-
-    // Get the inner-most uri for schemes like jar:
-    if (uri instanceof Ci.nsINestedURI)
-      uri = uri.innermostURI;
-
-    let fullHost;
-    try {
-      // Get the full host name; some special URIs fail (data: jar:)
-      fullHost = uri.host;
-    } catch (e) {
-      fullHost = "";
-    }
-
-    let displayHost;
-    try {
-      // This might fail if it's an IP address or doesn't have more than 1 part
-      let baseDomain = eTLDService.getBaseDomain(uri);
-
-      // Convert base domain for display; ignore the isAscii out param
-      displayHost = idnService.convertToDisplayIDN(baseDomain, {});
-    } catch (e) {
-      // Default to the host name
-      displayHost = fullHost;
-    }
-
-    // Check if we need to show something else for the host
-    if (uri.scheme == "file") {
-      // Display special text for file protocol
-      displayHost = gStr.doneFileScheme;
-      fullHost = displayHost;
-    } else if (displayHost.length == 0) {
-      // Got nothing; show the scheme (data: about: moz-icon:)
-      displayHost = replaceInsert(gStr.doneScheme, 1, uri.scheme);
-      fullHost = displayHost;
-    } else if (uri.port != -1) {
-      // Tack on the port if it's not the default port
-      let port = ":" + uri.port;
-      displayHost += port;
-      fullHost += port;
-    }
-
-    return [displayHost, fullHost];
-  },
-
-  /**
-   * Converts a number of bytes to the appropriate unit that results in a
-   * number that needs fewer than 4 digits
-   *
-   * @param aBytes
-   *        Number of bytes to convert
-   * @return A pair: [new value with 3 sig. figs., its unit]
-   */
-  convertByteUnits: function DU_convertByteUnits(aBytes)
-  {
-    let unitIndex = 0;
-
-    // Convert to next unit if it needs 4 digits (after rounding), but only if
-    // we know the name of the next unit
-    while ((aBytes >= 999.5) && (unitIndex < gStr.units.length - 1)) {
-      aBytes /= 1024;
-      unitIndex++;
-    }
-
-    // Get rid of insignificant bits by truncating to 1 or 0 decimal points
-    // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
-    aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
-
-    return [aBytes, gStr.units[unitIndex]];
-  },
-
-  /**
-   * Converts a number of seconds to the two largest units. Time values are
-   * whole numbers, and units have the correct plural/singular form.
-   *
-   * @param aSecs
-   *        Seconds to convert into the appropriate 2 units
-   * @return 4-item array [first value, its unit, second value, its unit]
-   */
-  convertTimeUnits: function DU_convertTimeUnits(aSecs)
-  {
-    // These are the maximum values for seconds, minutes, hours corresponding
-    // with gStr.timeUnits without the last item
-    let timeSize = [60, 60, 24];
-
-    let time = aSecs;
-    let scale = 1;
-    let unitIndex = 0;
-
-    // Keep converting to the next unit while we have units left and the
-    // current one isn't the largest unit possible
-    while ((unitIndex < timeSize.length) && (time >= timeSize[unitIndex])) {
-      time /= timeSize[unitIndex];
-      scale *= timeSize[unitIndex];
-      unitIndex++;
-    }
-
-    let value = convertTimeUnitsValue(time);
-    let units = convertTimeUnitsUnits(value, unitIndex);
-
-    let extra = aSecs - value * scale;
-    let nextIndex = unitIndex - 1;
-
-    // Convert the extra time to the next largest unit
-    for (let index = 0; index < nextIndex; index++)
-      extra /= timeSize[index];
-
-    let value2 = convertTimeUnitsValue(extra);
-    let units2 = convertTimeUnitsUnits(value2, nextIndex);
-
-    return [value, units, value2, units2];
-  },
-};
-
-/**
- * Private helper for convertTimeUnits that gets the display value of a time
- *
- * @param aTime
- *        Time value for display
- * @return An integer value for the time rounded down
- */
-function convertTimeUnitsValue(aTime)
-{
-  return Math.floor(aTime);
-}
-
-/**
- * Private helper for convertTimeUnits that gets the display units of a time
- *
- * @param aTime
- *        Time value for display
- * @param aIndex
- *        Index into gStr.timeUnits for the appropriate unit
- * @return The appropriate plural form of the unit for the time
- */
-function convertTimeUnitsUnits(aTime, aIndex)
-{
-  // Negative index would be an invalid unit, so just give empty
-  if (aIndex < 0)
-    return "";
-
-  return PluralForm.get(aTime, gStr.timeUnits[aIndex]);
-}
-
-/**
- * Private helper function to replace a placeholder string with a real string
- *
- * @param aText
- *        Source text containing placeholder (e.g., #1)
- * @param aIndex
- *        Index number of placeholder to replace
- * @param aValue
- *        New string to put in place of placeholder
- * @return The string with placeholder replaced with the new string
- */
-function replaceInsert(aText, aIndex, aValue)
-{
-  return aText.replace("#" + aIndex, aValue);
-}
-
-/**
- * Private helper function to log errors to the error console and command line
- *
- * @param aMsg
- *        Error message to log or an array of strings to concat
- */
-function log(aMsg)
-{
-  let msg = "DownloadUtils.jsm: " + (aMsg.join ? aMsg.join("") : aMsg);
-  Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).
-    logStringMessage(msg);
-  dump(msg + "\n");
-}
deleted file mode 100644
--- a/toolkit/mozapps/downloads/src/Makefile.in
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is mozilla.org code.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 1998
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-DEPTH   = ../../../..
-topsrcdir = @top_srcdir@
-srcdir    = @srcdir@
-VPATH   = @srcdir@
-
-include $(DEPTH)/config/autoconf.mk
-
-MODULE = helperAppDlg
-
-EXTRA_COMPONENTS = nsHelperAppDlg.js
-GARBAGE += nsHelperAppDlg.js
-
-EXTRA_JS_MODULES = \
-  DownloadUtils.jsm \
-  DownloadLastDir.jsm \
-  $(NULL)
-
-include $(topsrcdir)/config/rules.mk
-
-nsHelperAppDlg.js: nsHelperAppDlg.js.in
-	$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $^ > $@ 
-
deleted file mode 100644
--- a/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
+++ /dev/null
@@ -1,1238 +0,0 @@
-/*
-# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is Mozilla.org Code.
-#
-# The Initial Developer of the Original Code is
-# Doron Rosenberg.
-# Portions created by the Initial Developer are Copyright (C) 2001
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#   Bill Law <law@netscape.com>
-#   Scott MacGregor <mscott@netscape.com>
-#   Ben Goodger <ben@bengoodger.com> (2.0)
-#   Fredrik Holmqvist <thesuckiestemail@yahoo.se>
-#   Dan Mosedale <dmose@mozilla.org>
-#   Jim Mathies <jmathies@mozilla.com>
-#   Ehsan Akhgari <ehsan.akhgari@gmail.com>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either the GNU General Public License Version 2 or later (the "GPL"), or
-# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-*/
-
-///////////////////////////////////////////////////////////////////////////////
-//// Helper Functions
-
-/**
- * Determines if a given directory is able to be used to download to.
- *
- * @param aDirectory
- *        The directory to check.
- * @returns true if we can use the directory, false otherwise.
- */
-function isUsableDirectory(aDirectory)
-{
-  return aDirectory.exists() && aDirectory.isDirectory() &&
-         aDirectory.isWritable();
-}
-
-///////////////////////////////////////////////////////////////////////////////
-//// nsUnkownContentTypeDialog
-
-/* This file implements the nsIHelperAppLauncherDialog interface.
- *
- * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
- * comprised of:
- *   - a JS constructor function
- *   - a prototype providing all the interface methods and implementation stuff
- *
- * In addition, this file implements an nsIModule object that registers the
- * nsUnknownContentTypeDialog component.
- */
-
-const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
-const nsITimer = Components.interfaces.nsITimer;
-
-Components.utils.import("resource://gre/modules/DownloadLastDir.jsm");
-
-/* ctor
- */
-function nsUnknownContentTypeDialog() {
-    // Initialize data properties.
-    this.mLauncher = null;
-    this.mContext  = null;
-    this.mSourcePath = null;
-    this.chosenApp = null;
-    this.givenDefaultApp = false;
-    this.updateSelf = true;
-    this.mTitle    = "";
-}
-
-nsUnknownContentTypeDialog.prototype = {
-    nsIMIMEInfo  : Components.interfaces.nsIMIMEInfo,
-
-    QueryInterface: function (iid) {
-        if (!iid.equals(Components.interfaces.nsIHelperAppLauncherDialog) &&
-            !iid.equals(Components.interfaces.nsITimerCallback) &&
-            !iid.equals(Components.interfaces.nsISupports)) {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-        return this;
-    },
-
-    // ---------- nsIHelperAppLauncherDialog methods ----------
-
-    // show: Open XUL dialog using window watcher.  Since the dialog is not
-    //       modal, it needs to be a top level window and the way to open
-    //       one of those is via that route).
-    show: function(aLauncher, aContext, aReason)  {
-      this.mLauncher = aLauncher;
-      this.mContext  = aContext;
-
-      const nsITimer = Components.interfaces.nsITimer;
-      this._showTimer = Components.classes["@mozilla.org/timer;1"]
-                                  .createInstance(nsITimer);
-      this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
-    },
-
-    // When opening from new tab, if tab closes while dialog is opening,
-    // (which is a race condition on the XUL file being cached and the timer
-    // in nsExternalHelperAppService), the dialog gets a blur and doesn't
-    // activate the OK button.  So we wait a bit before doing opening it.
-    reallyShow: function() {
-        try {
-          var ir = this.mContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
-          var dwi = ir.getInterface(Components.interfaces.nsIDOMWindowInternal);
-          var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
-                             .getService(Components.interfaces.nsIWindowWatcher);
-          this.mDialog = ww.openWindow(dwi,
-                                       "chrome://mozapps/content/downloads/unknownContentType.xul",
-                                       null,
-                                       "chrome,centerscreen,titlebar,dialog=yes,dependent",
-                                       null);
-        } catch (ex) {
-          // The containing window may have gone away.  Break reference
-          // cycles and stop doing the download.
-          const NS_BINDING_ABORTED = 0x804b0002;
-          this.mLauncher.cancel(NS_BINDING_ABORTED);
-          return;
-        }
-
-        // Hook this object to the dialog.
-        this.mDialog.dialog = this;
-
-        // Hook up utility functions.
-        this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;
-
-        // Watch for error notifications.
-        this.progressListener.helperAppDlg = this;
-        this.mLauncher.setWebProgressListener(this.progressListener);
-    },
-
-    // promptForSaveToFile:  Display file picker dialog and return selected file.
-    //                       This is called by the External Helper App Service
-    //                       after the ucth dialog calls |saveToDisk| with a null
-    //                       target filename (no target, therefore user must pick).
-    //
-    //                       Alternatively, if the user has selected to have all
-    //                       files download to a specific location, return that
-    //                       location and don't ask via the dialog. 
-    //
-    // Note - this function is called without a dialog, so it cannot access any part
-    // of the dialog XUL as other functions on this object do. 
-    promptForSaveToFile: function(aLauncher, aContext, aDefaultFile, aSuggestedFileExtension, aForcePrompt) {
-      var result = null;
-      
-      this.mLauncher = aLauncher;
-
-      let prefs = Components.classes["@mozilla.org/preferences-service;1"]
-                            .getService(Components.interfaces.nsIPrefBranch);
-      let bundle = Components.classes["@mozilla.org/intl/stringbundle;1"].
-        getService(Components.interfaces.nsIStringBundleService).
-        createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
-
-      if (!aForcePrompt) {
-        // Check to see if the user wishes to auto save to the default download
-        // folder without prompting. Note that preference might not be set.
-        let autodownload = false;
-        try {
-          autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
-        } catch (e) { }
-      
-        if (autodownload) {
-          // Retrieve the user's default download directory
-          let dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
-                                  .getService(Components.interfaces.nsIDownloadManager);
-          let defaultFolder = dnldMgr.userDownloadsDirectory;
-
-          try {
-            result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension);
-          }
-          catch (ex) {
-            if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) {
-              let prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
-                getService(Components.interfaces.nsIPromptService);
-
-              // Display error alert (using text supplied by back-end)
-              prompter.alert(this.dialog,
-                             bundle.GetStringFromName("badPermissions.title"),
-                             bundle.GetStringFromName("badPermissions"));
-
-              return;
-            }
-          }
-
-          // Check to make sure we have a valid directory, otherwise, prompt
-          if (result)
-            return result;
-        }
-      }
-      
-      // Use file picker to show dialog.
-      var nsIFilePicker = Components.interfaces.nsIFilePicker;
-      var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
-      var windowTitle = bundle.GetStringFromName("saveDialogTitle");
-      var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowInternal);
-      picker.init(parent, windowTitle, nsIFilePicker.modeSave);
-      picker.defaultString = aDefaultFile;
-
-      if (aSuggestedFileExtension) {
-        // aSuggestedFileExtension includes the period, so strip it
-        picker.defaultExtension = aSuggestedFileExtension.substring(1);
-      } 
-      else {
-        try {
-          picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
-        } 
-        catch (ex) { }
-      }
-
-      var wildCardExtension = "*";
-      if (aSuggestedFileExtension) {
-        wildCardExtension += aSuggestedFileExtension;
-        picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension);
-      }
-
-      picker.appendFilters( nsIFilePicker.filterAll );
-
-      // Default to lastDir if it is valid, otherwise use the user's default
-      // downloads directory.  userDownloadsDirectory should always return a
-      // valid directory, so we can safely default to it.
-      var dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
-                              .getService(Components.interfaces.nsIDownloadManager);
-      picker.displayDirectory = dnldMgr.userDownloadsDirectory;
-
-      // The last directory preference may not exist, which will throw.
-      try {
-        var lastDir = gDownloadLastDir.file;
-        if (isUsableDirectory(lastDir))
-          picker.displayDirectory = lastDir;
-      }
-      catch (ex) {
-      }
-
-      if (picker.show() == nsIFilePicker.returnCancel) {
-        // null result means user cancelled.
-        return null;
-      }
-
-      // Be sure to save the directory the user chose through the Save As... 
-      // dialog  as the new browser.download.dir since the old one
-      // didn't exist.
-      result = picker.file;
-
-      if (result) {
-        try {
-          // Remove the file so that it's not there when we ensure non-existence later;
-          // this is safe because for the file to exist, the user would have had to
-          // confirm that he wanted the file overwritten.
-          if (result.exists())
-            result.remove(false);
-        }
-        catch (e) { }
-        var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile);
-
-        // Do not store the last save directory as a pref inside the private browsing mode
-        gDownloadLastDir.file = newDir;
-
-        result = this.validateLeafName(newDir, result.leafName, null);
-      }
-      return result;
-    },
-
-    /**
-     * Ensures that a local folder/file combination does not already exist in
-     * the file system (or finds such a combination with a reasonably similar
-     * leaf name), creates the corresponding file, and returns it.
-     *
-     * @param   aLocalFile
-     *          the folder where the file resides
-     * @param   aLeafName
-     *          the string name of the file (may be empty if no name is known,
-     *          in which case a name will be chosen)
-     * @param   aFileExt
-     *          the extension of the file, if one is known; this will be ignored
-     *          if aLeafName is non-empty
-     * @returns nsILocalFile
-     *          the created file
-     */
-    validateLeafName: function (aLocalFile, aLeafName, aFileExt)
-    {
-      if (!(aLocalFile && isUsableDirectory(aLocalFile)))
-        return null;
-
-      // Remove any leading periods, since we don't want to save hidden files
-      // automatically.
-      aLeafName = aLeafName.replace(/^\.+/, "");
-
-      if (aLeafName == "")
-        aLeafName = "unnamed" + (aFileExt ? "." + aFileExt : "");
-      aLocalFile.append(aLeafName);
-
-      this.makeFileUnique(aLocalFile);
-
-#ifdef XP_WIN
-      let ext;
-      try {
-        // We can fail here if there's no primary extension set
-        ext = "." + this.mLauncher.MIMEInfo.primaryExtension;
-      } catch (e) { }
-
-      // Append a file extension if it's an executable that doesn't have one
-      // but make sure we actually have an extension to add
-      let leaf = aLocalFile.leafName;
-      if (aLocalFile.isExecutable() && ext &&
-          leaf.substring(leaf.length - ext.length) != ext) {
-        let f = aLocalFile.clone();
-        aLocalFile.leafName = leaf + ext;
-
-        f.remove(false);
-        this.makeFileUnique(aLocalFile);
-      }
-#endif
-
-      return aLocalFile;
-    },
-
-    /**
-     * Generates and returns a uniquely-named file from aLocalFile.  If
-     * aLocalFile does not exist, it will be the file returned; otherwise, a
-     * file whose name is similar to that of aLocalFile will be returned.
-     */
-    makeFileUnique: function (aLocalFile)
-    {
-      try {
-        // Note - this code is identical to that in 
-        //   toolkit/content/contentAreaUtils.js.
-        // If you are updating this code, update that code too! We can't share code
-        // here since this is called in a js component. 
-        var collisionCount = 0;
-        while (aLocalFile.exists()) {
-          collisionCount++;
-          if (collisionCount == 1) {
-            // Append "(2)" before the last dot in (or at the end of) the filename
-            // special case .ext.gz etc files so we don't wind up with .tar(2).gz
-            if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) {
-              aLocalFile.leafName = aLocalFile.leafName.replace(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i, "(2)$&");
-            }
-            else {
-              aLocalFile.leafName = aLocalFile.leafName.replace(/(\.[^\.]*)?$/, "(2)$&");
-            }
-          }
-          else {
-            // replace the last (n) in the filename with (n+1)
-            aLocalFile.leafName = aLocalFile.leafName.replace(/^(.*\()\d+\)/, "$1" + (collisionCount+1) + ")");
-          }
-        }
-        aLocalFile.create(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
-      }
-      catch (e) {
-        dump("*** exception in validateLeafName: " + e + "\n");
-
-        if (e.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED)
-          throw e;
-
-        if (aLocalFile.leafName == "" || aLocalFile.isDirectory()) {
-          aLocalFile.append("unnamed");
-          if (aLocalFile.exists())
-            aLocalFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
-        }
-      }
-    },
-    
-    // ---------- implementation methods ----------
-
-    // Web progress listener so we can detect errors while mLauncher is
-    // streaming the data to a temporary file.
-    progressListener: {
-        // Implementation properties.
-        helperAppDlg: null,
-
-        // nsIWebProgressListener methods.
-        // Look for error notifications and display alert to user.
-        onStatusChange: function( aWebProgress, aRequest, aStatus, aMessage ) {
-            if ( aStatus != Components.results.NS_OK ) {
-                // Get prompt service.
-                var prompter = Components.classes[ "@mozilla.org/embedcomp/prompt-service;1" ]
-                                   .getService( Components.interfaces.nsIPromptService );
-                // Display error alert (using text supplied by back-end).
-                prompter.alert( this.dialog, this.helperAppDlg.mTitle, aMessage );
-
-                // Close the dialog.
-                this.helperAppDlg.onCancel();
-                if ( this.helperAppDlg.mDialog ) {
-                    this.helperAppDlg.mDialog.close();
-                }
-            }
-        },
-
-        // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, and onRefreshAttempted notifications.
-        onProgressChange: function( aWebProgress,
-                                    aRequest,
-                                    aCurSelfProgress,
-                                    aMaxSelfProgress,
-                                    aCurTotalProgress,
-                                    aMaxTotalProgress ) {
-        },
-
-        onProgressChange64: function( aWebProgress,
-                                      aRequest,
-                                      aCurSelfProgress,
-                                      aMaxSelfProgress,
-                                      aCurTotalProgress,
-                                      aMaxTotalProgress ) {
-        },
-
-
-
-        onStateChange: function( aWebProgress, aRequest, aStateFlags, aStatus ) {
-        },
-
-        onLocationChange: function( aWebProgress, aRequest, aLocation ) {
-        },
-
-        onSecurityChange: function( aWebProgress, aRequest, state ) {
-        },
-
-        onRefreshAttempted: function( aWebProgress, aURI, aDelay, aSameURI ) {
-          return true;
-	}
-    },
-
-    // initDialog:  Fill various dialog fields with initial content.
-    initDialog : function() {
-      // Put file name in window title.
-      var suggestedFileName = this.mLauncher.suggestedFileName;
-
-      // Some URIs do not implement nsIURL, so we can't just QI.
-      var url   = this.mLauncher.source;
-      var fname = "";
-      this.mSourcePath = url.prePath;
-      try {
-          url = url.QueryInterface( Components.interfaces.nsIURL );
-          // A url, use file name from it.
-          fname = url.fileName;
-          this.mSourcePath += url.directory;
-      } catch (ex) {
-          // A generic uri, use path.
-          fname = url.path;
-          this.mSourcePath += url.path;
-      }
-
-      if (suggestedFileName)
-        fname = suggestedFileName;
-      
-      var displayName = fname.replace(/ +/g, " ");
-
-      this.mTitle = this.dialogElement("strings").getFormattedString("title", [displayName]);
-      this.mDialog.document.title = this.mTitle;
-
-      // Put content type, filename and location into intro.
-      this.initIntro(url, fname, displayName);
-
-      var iconString = "moz-icon://" + fname + "?size=16&contentType=" + this.mLauncher.MIMEInfo.MIMEType;
-      this.dialogElement("contentTypeImage").setAttribute("src", iconString);
-
-      // if always-save and is-executable and no-handler
-      // then set up simple ui
-      var mimeType = this.mLauncher.MIMEInfo.MIMEType;
-      var shouldntRememberChoice = (mimeType == "application/octet-stream" || 
-                                    mimeType == "application/x-msdownload" ||
-                                    this.mLauncher.targetFileIsExecutable);
-      if (shouldntRememberChoice && !this.openWithDefaultOK()) {
-        // hide featured choice 
-        this.dialogElement("normalBox").collapsed = true;
-        // show basic choice 
-        this.dialogElement("basicBox").collapsed = false;
-        // change button labels and icons; use "save" icon for the accept
-        // button since it's the only action possible
-        let acceptButton = this.mDialog.document.documentElement
-                               .getButton("accept");
-        acceptButton.label = this.dialogElement("strings")
-                                 .getString("unknownAccept.label");
-        acceptButton.setAttribute("icon", "save");
-        this.mDialog.document.documentElement.getButton("cancel").label = this.dialogElement("strings").getString("unknownCancel.label");
-        // hide other handler
-        this.dialogElement("openHandler").collapsed = true;
-        // set save as the selected option
-        this.dialogElement("mode").selectedItem = this.dialogElement("save");
-      }
-      else {
-        this.initAppAndSaveToDiskValues();
-
-        // Initialize "always ask me" box. This should always be disabled
-        // and set to true for the ambiguous type application/octet-stream.
-        // We don't also check for application/x-msdownload here since we
-        // want users to be able to autodownload .exe files. 
-        var rememberChoice = this.dialogElement("rememberChoice");
-
-#if 0
-        // Just because we have a content-type of application/octet-stream 
-        // here doesn't actually mean that the content is of that type. Many 
-        // servers default to sending text/plain for file types they don't know
-        // about. To account for this, the uriloader does some checking to see 
-        // if a file sent as text/plain contains binary characters, and if so (*)
-        // it morphs the content-type into application/octet-stream so that
-        // the file can be properly handled. Since this is not generic binary
-        // data, rather, a data format that the system probably knows about, 
-        // we don't want to use the content-type provided by this dialog's 
-        // opener, as that's the generic application/octet-stream that the 
-        // uriloader has passed, rather we want to ask the MIME Service.
-        // This is so we don't needlessly disable the "autohandle" checkbox.
-        var mimeService = Components.classes["@mozilla.org/mime;1"].getService(Components.interfaces.nsIMIMEService);
-        var type = mimeService.getTypeFromURI(this.mLauncher.source);
-        this.realMIMEInfo = mimeService.getFromTypeAndExtension(type, "");
-
-        if (type == "application/octet-stream") {
-#endif
-        if (shouldntRememberChoice) {
-          rememberChoice.checked = false;
-          rememberChoice.disabled = true;
-        }
-        else {
-          rememberChoice.checked = !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
-        }
-        this.toggleRememberChoice(rememberChoice);
-
-        // XXXben - menulist won't init properly, hack. 
-        var openHandler = this.dialogElement("openHandler");
-        openHandler.parentNode.removeChild(openHandler);
-        var openHandlerBox = this.dialogElement("openHandlerBox");
-        openHandlerBox.appendChild(openHandler);
-      }
-
-      this.mDialog.setTimeout("dialog.postShowCallback()", 0);
-      
-      this.mDialog.document.documentElement.getButton("accept").disabled = true;
-      this._showTimer = Components.classes["@mozilla.org/timer;1"]
-                                  .createInstance(nsITimer);
-      this._showTimer.initWithCallback(this, 250, nsITimer.TYPE_ONE_SHOT);
-    },
-
-    notify: function (aTimer) {
-      if (aTimer == this._showTimer) {
-        if (!this.mDialog) {
-          this.reallyShow();
-        } else {
-          // The user may have already canceled the dialog.
-          try {
-            if (!this._blurred) {
-              this.mDialog.document.documentElement.getButton("accept").disabled = false;
-            }
-          } catch (ex) {}
-          this._delayExpired = true;
-        }
-        // The timer won't release us, so we have to release it.
-        this._showTimer = null;
-      }
-      else if (aTimer == this._saveToDiskTimer) {
-        // Since saveToDisk may open a file picker and therefore block this routine,
-        // we should only call it once the dialog is closed.
-        this.mLauncher.saveToDisk(null, false);
-        this._saveToDiskTimer = null;
-      }
-    },
-
-    postShowCallback: function () {
-      this.mDialog.sizeToContent();
-
-      // Set initial focus
-      this.dialogElement("mode").focus();
-    },
-
-    // initIntro:
-    initIntro: function(url, filename, displayname) {
-        this.dialogElement( "location" ).value = displayname;
-        this.dialogElement( "location" ).setAttribute("realname", filename);
-        this.dialogElement( "location" ).setAttribute("tooltiptext", displayname);
-
-        // if mSourcePath is a local file, then let's use the pretty path name instead of an ugly
-        // url...
-        var pathString = this.mSourcePath;
-        try 
-        {
-          var fileURL = url.QueryInterface(Components.interfaces.nsIFileURL);
-          if (fileURL)
-          {
-            var fileObject = fileURL.file;
-            if (fileObject)
-            {
-              var parentObject = fileObject.parent;
-              if (parentObject)
-              {
-                pathString = parentObject.path;
-              }
-            }
-          }
-        } catch(ex) {}
-
-        if (pathString == this.mSourcePath)
-        {
-          // wasn't a fileURL
-          var tmpurl = url.clone(); // don't want to change the real url
-          try {
-            tmpurl.userPass = "";
-          } catch (ex) {}
-          pathString = tmpurl.prePath;
-        }
-
-        // Set the location text, which is separate from the intro text so it can be cropped
-        var location = this.dialogElement( "source" );
-        location.value = pathString;
-        location.setAttribute("tooltiptext", this.mSourcePath);
-        
-        // Show the type of file. 
-        var type = this.dialogElement("type");
-        var mimeInfo = this.mLauncher.MIMEInfo;
-        
-        // 1. Try to use the pretty description of the type, if one is available.
-        var typeString = mimeInfo.description;
-        
-        if (typeString == "") {
-          // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
-          var primaryExtension = "";
-          try {
-            primaryExtension = mimeInfo.primaryExtension;
-          }
-          catch (ex) {
-          }
-          if (primaryExtension != "")
-            typeString = this.dialogElement("strings").getFormattedString("fileType", [primaryExtension.toUpperCase()]);
-          // 3. If we can't even do that, just give up and show the MIME type. 
-          else
-            typeString = mimeInfo.MIMEType;
-        }
-        
-        type.value = typeString;
-    },
-    
-    _blurred: false,
-    _delayExpired: false, 
-    onBlur: function(aEvent) {
-      this._blurred = true;
-      this.mDialog.document.documentElement.getButton("accept").disabled = true;
-    },
-    
-    onFocus: function(aEvent) {
-      this._blurred = false;
-      if (this._delayExpired) {
-        var script = "document.documentElement.getButton('accept').disabled = false";
-        this.mDialog.setTimeout(script, 250);
-      }
-    },
-
-    // Returns true if opening the default application makes sense.
-    openWithDefaultOK: function() {
-        // The checking is different on Windows...
-#ifdef XP_WIN
-        // Windows presents some special cases.
-        // We need to prevent use of "system default" when the file is
-        // executable (so the user doesn't launch nasty programs downloaded
-        // from the web), and, enable use of "system default" if it isn't
-        // executable (because we will prompt the user for the default app
-        // in that case).
-        
-        //  Default is Ok if the file isn't executable (and vice-versa).
-        return !this.mLauncher.targetFileIsExecutable;
-#else
-            // On other platforms, default is Ok if there is a default app.
-            // Note that nsIMIMEInfo providers need to ensure that this holds true
-            // on each platform.
-        return this.mLauncher.MIMEInfo.hasDefaultHandler;
-#endif
-    },
-    
-    // Set "default" application description field.
-    initDefaultApp: function() {
-      // Use description, if we can get one.
-      var desc = this.mLauncher.MIMEInfo.defaultDescription;
-      if (desc) {
-        var defaultApp = this.dialogElement("strings").getFormattedString("defaultApp", [desc]);
-        this.dialogElement("defaultHandler").label = defaultApp;
-      }
-      else {
-        this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
-        // Hide the default handler item too, in case the user picks a 
-        // custom handler at a later date which triggers the menulist to show.
-        this.dialogElement("defaultHandler").hidden = true;
-      }
-    },
-
-    // getPath:
-    getPath: function (aFile) {
-#ifdef XP_MACOSX
-      return aFile.leafName || aFile.path;
-#else
-      return aFile.path;
-#endif
-    },
-
-    // initAppAndSaveToDiskValues:
-    initAppAndSaveToDiskValues: function() {
-      var modeGroup = this.dialogElement("mode");
-
-      // We don't let users open .exe files or random binary data directly 
-      // from the browser at the moment because of security concerns. 
-      var openWithDefaultOK = this.openWithDefaultOK();
-      var mimeType = this.mLauncher.MIMEInfo.MIMEType;
-      if (this.mLauncher.targetFileIsExecutable || (
-          (mimeType == "application/octet-stream" ||
-           mimeType == "application/x-msdownload") && 
-           !openWithDefaultOK)) {
-        this.dialogElement("open").disabled = true;
-        var openHandler = this.dialogElement("openHandler");
-        openHandler.disabled = true;
-        openHandler.selectedItem = null;
-        modeGroup.selectedItem = this.dialogElement("save");
-        return;
-      }
-    
-      // Fill in helper app info, if there is any.
-      try {
-        this.chosenApp =
-          this.mLauncher.MIMEInfo.preferredApplicationHandler
-              .QueryInterface(Components.interfaces.nsILocalHandlerApp);
-      } catch (e) {
-        this.chosenApp = null;
-      }
-      // Initialize "default application" field.
-      this.initDefaultApp();
-
-      var otherHandler = this.dialogElement("otherHandler");
-              
-      // Fill application name textbox.
-      if (this.chosenApp && this.chosenApp.executable && 
-          this.chosenApp.executable.path) {
-        otherHandler.setAttribute("path",
-                                  this.getPath(this.chosenApp.executable));
-
-#if XP_MACOSX
-        this.chosenApp.executable.QueryInterface(Components.interfaces.nsILocalFileMac);
-        otherHandler.label = this.chosenApp.executable.bundleDisplayName;
-#else
-        otherHandler.label = this.chosenApp.executable.leafName;
-#endif
-        otherHandler.hidden = false;
-      }
-
-      var useDefault = this.dialogElement("useSystemDefault");
-      var openHandler = this.dialogElement("openHandler");
-      openHandler.selectedIndex = 0;
-
-      if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useSystemDefault) {
-        // Open (using system default).
-        modeGroup.selectedItem = this.dialogElement("open");
-      } else if (this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp) {
-        // Open with given helper app.
-        modeGroup.selectedItem = this.dialogElement("open");
-        openHandler.selectedIndex = 1;
-      } else {
-        // Save to disk.
-        modeGroup.selectedItem = this.dialogElement("save");
-      }
-      
-      // If we don't have a "default app" then disable that choice.
-      if (!openWithDefaultOK) {
-        var useDefault = this.dialogElement("defaultHandler");
-        var isSelected = useDefault.selected;
-        
-        // Disable that choice.
-        useDefault.hidden = true;
-        // If that's the default, then switch to "save to disk."
-        if (isSelected) {
-          openHandler.selectedIndex = 1;
-          modeGroup.selectedItem = this.dialogElement("save");
-        }
-      }
-      
-      otherHandler.nextSibling.hidden = otherHandler.nextSibling.nextSibling.hidden = false;
-      this.updateOKButton();
-    },
-
-    // Returns the user-selected application
-    helperAppChoice: function() {
-      return this.chosenApp;
-    },
-    
-    get saveToDisk() {
-      return this.dialogElement("save").selected;
-    },
-    
-    get useOtherHandler() {
-      return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 1;
-    },
-    
-    get useSystemDefault() {
-      return this.dialogElement("open").selected && this.dialogElement("openHandler").selectedIndex == 0;
-    },
-    
-    toggleRememberChoice: function (aCheckbox) {
-        this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
-        this.mDialog.sizeToContent();
-    },
-    
-    openHandlerCommand: function () {
-      var openHandler = this.dialogElement("openHandler");
-      if (openHandler.selectedItem.id == "choose")
-        this.chooseApp();
-      else
-        openHandler.setAttribute("lastSelectedItemID", openHandler.selectedItem.id);
-    },
-
-    updateOKButton: function() {
-      var ok = false;
-      if (this.dialogElement("save").selected) {
-        // This is always OK.
-        ok = true;
-      } 
-      else if (this.dialogElement("open").selected) {
-        switch (this.dialogElement("openHandler").selectedIndex) {
-        case 0:
-          // No app need be specified in this case.
-          ok = true;
-          break;
-        case 1:
-          // only enable the OK button if we have a default app to use or if 
-          // the user chose an app....
-          ok = this.chosenApp || /\S/.test(this.dialogElement("otherHandler").getAttribute("path")); 
-        break;
-        }
-      }
-
-      // Enable Ok button if ok to press.
-      this.mDialog.document.documentElement.getButton("accept").disabled = !ok;
-    },
-    
-    // Returns true iff the user-specified helper app has been modified.
-    appChanged: function() {
-      return this.helperAppChoice() != this.mLauncher.MIMEInfo.preferredApplicationHandler;
-    },
-
-    updateMIMEInfo: function() {
-      var needUpdate = false;
-      // If current selection differs from what's in the mime info object,
-      // then we need to update.
-      if (this.saveToDisk) {
-        needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
-        if (needUpdate)
-          this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
-      } 
-      else if (this.useSystemDefault) {
-        needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useSystemDefault;
-        if (needUpdate)
-          this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useSystemDefault;
-      } 
-      else {
-        // For "open with", we need to check both preferred action and whether the user chose
-        // a new app.
-        needUpdate = this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.useHelperApp || this.appChanged();
-        if (needUpdate) {
-          this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
-          // App may have changed - Update application
-          var app = this.helperAppChoice();
-          this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
-        }
-      }
-      // We will also need to update if the "always ask" flag has changed.
-      needUpdate = needUpdate || this.mLauncher.MIMEInfo.alwaysAskBeforeHandling != (!this.dialogElement("rememberChoice").checked);
-
-      // One last special case: If the input "always ask" flag was false, then we always
-      // update.  In that case we are displaying the helper app dialog for the first
-      // time for this mime type and we need to store the user's action in the mimeTypes.rdf
-      // data source (whether that action has changed or not; if it didn't change, then we need
-      // to store the "always ask" flag so the helper app dialog will or won't display
-      // next time, per the user's selection).
-      needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;
-
-      // Make sure mime info has updated setting for the "always ask" flag.
-      this.mLauncher.MIMEInfo.alwaysAskBeforeHandling = !this.dialogElement("rememberChoice").checked;
-
-      return needUpdate;        
-    },
-    
-    // See if the user changed things, and if so, update the
-    // mimeTypes.rdf entry for this mime type.
-    updateHelperAppPref: function() {
-      var ha = new this.mDialog.HelperApps();
-      ha.updateTypeInfo(this.mLauncher.MIMEInfo);
-      ha.destroy();
-    },
-    
-    // onOK:
-    onOK: function() {
-      // Verify typed app path, if necessary.
-      if (this.useOtherHandler) {
-        var helperApp = this.helperAppChoice();
-        if (!helperApp || !helperApp.executable ||
-            !helperApp.executable.exists()) {
-          // Show alert and try again.        
-          var bundle = this.dialogElement("strings");                    
-          var msg = bundle.getFormattedString("badApp", [this.dialogElement("otherHandler").getAttribute("path")]);
-          var svc = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService(Components.interfaces.nsIPromptService);
-          svc.alert(this.mDialog, bundle.getString("badApp.title"), msg);
-
-          // Disable the OK button.
-          this.mDialog.document.documentElement.getButton("accept").disabled = true;
-          this.dialogElement("mode").focus();          
-
-          // Clear chosen application.
-          this.chosenApp = null;
-
-          // Leave dialog up.
-          return false;
-        }
-      }
-        
-      // Remove our web progress listener (a progress dialog will be
-      // taking over).
-      this.mLauncher.setWebProgressListener(null);
-
-      // saveToDisk and launchWithApplication can return errors in 
-      // certain circumstances (e.g. The user clicks cancel in the
-      // "Save to Disk" dialog. In those cases, we don't want to
-      // update the helper application preferences in the RDF file.
-      try {
-        var needUpdate = this.updateMIMEInfo();
-        
-        if (this.dialogElement("save").selected) {
-          // If we're using a default download location, create a path
-          // for the file to be saved to to pass to |saveToDisk| - otherwise
-          // we must ask the user to pick a save name.
-
-#if 0
-          var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
-          var targetFile = null;
-          try {
-            targetFile = prefs.getComplexValue("browser.download.defaultFolder", 
-                                               Components.interfaces.nsILocalFile);
-            var leafName = this.dialogElement("location").getAttribute("realname");
-            // Ensure that we don't overwrite any existing files here. 
-            targetFile = this.validateLeafName(targetFile, leafName, null);
-          }
-          catch(e) { }
-
-          this.mLauncher.saveToDisk(targetFile, false);
-#endif
-
-          // see @notify
-          // we cannot use opener's setTimeout, see bug 420405
-          this._saveToDiskTimer = Components.classes["@mozilla.org/timer;1"]
-                                            .createInstance(nsITimer);
-          this._saveToDiskTimer.initWithCallback(this, 0,
-                                                 nsITimer.TYPE_ONE_SHOT);
-        }
-        else
-          this.mLauncher.launchWithApplication(null, false);
-
-        // Update user pref for this mime type (if necessary). We do not
-        // store anything in the mime type preferences for the ambiguous
-        // type application/octet-stream. We do NOT do this for 
-        // application/x-msdownload since we want users to be able to 
-        // autodownload these to disk. 
-        if (needUpdate && this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream")
-          this.updateHelperAppPref();
-      } catch(e) { }
-
-      // Unhook dialog from this object.
-      this.mDialog.dialog = null;
-
-      // Close up dialog by returning true.
-      return true;
-    },
-
-    // onCancel:
-    onCancel: function() {
-      // Remove our web progress listener.
-      this.mLauncher.setWebProgressListener(null);
-
-      // Cancel app launcher.
-      try {
-        const NS_BINDING_ABORTED = 0x804b0002;
-        this.mLauncher.cancel(NS_BINDING_ABORTED);
-      } catch(exception) {
-      }
-
-      // Unhook dialog from this object.
-      this.mDialog.dialog = null;
-
-      // Close up dialog by returning true.
-      return true;
-    },
-
-    // dialogElement:  Convenience. 
-    dialogElement: function(id) {
-      return this.mDialog.document.getElementById(id);
-    },
-
-    // Retrieve the pretty description from the file
-    getFileDisplayName: function getFileDisplayName(file)
-    { 
-#ifdef XP_WIN
-        if (file instanceof Components.interfaces.nsILocalFileWin) {
-          try {
-            return file.getVersionInfoField("FileDescription");
-          } catch (ex) {
-          }
-        }
-#endif
-        return file.leafName;
-    },
-
-    // chooseApp:  Open file picker and prompt user for application.
-    chooseApp: function() {
-#ifdef XP_WIN
-    // Protect against the lack of an extension    
-    var fileExtension = "";
-    try {
-        fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
-    } catch(ex) {
-    }
-
-    // Try to use the pretty description of the type, if one is available.
-    var typeString = this.mLauncher.MIMEInfo.description;
-
-    if (!typeString) {
-      // If there is none, use the extension to 
-      // identify the file, e.g. "ZIP file"
-      if (fileExtension) {
-        typeString =
-          this.dialogElement("strings").
-          getFormattedString("fileType", [fileExtension.toUpperCase()]);
-      } else {
-        // If we can't even do that, just give up and show the MIME type.
-        typeString = this.mLauncher.MIMEInfo.MIMEType;
-      }
-    }
-
-    var params = {};
-    params.title = 
-      this.dialogElement("strings").getString("chooseAppFilePickerTitle");
-    params.description = typeString;
-    params.filename    = this.mLauncher.suggestedFileName;
-    params.mimeInfo    = this.mLauncher.MIMEInfo;
-    params.handlerApp  = null;
-
-    this.mDialog.openDialog("chrome://global/content/appPicker.xul", null,
-                            "chrome,modal,centerscreen,titlebar,dialog=yes",
-                            params);
-
-    if (params.handlerApp &&
-        params.handlerApp.executable &&
-        params.handlerApp.executable.isFile()) {
-        // Show the "handler" menulist since we have a (user-specified) 
-        // application now.
-        this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
-
-        // Remember the file they chose to run.
-        this.chosenApp = params.handlerApp;
-
-        // Update dialog
-        var otherHandler = this.dialogElement("otherHandler");
-        otherHandler.removeAttribute("hidden");
-        otherHandler.setAttribute("path",
-          this.getPath(this.chosenApp.executable));
-        otherHandler.label = 
-          this.getFileDisplayName(this.chosenApp.executable);
-        this.dialogElement("openHandler").selectedIndex = 1;
-        this.dialogElement("openHandler").setAttribute("lastSelectedItemID",
-          "otherHandler");
-        this.dialogElement("mode").selectedItem = this.dialogElement("open");
-    } else {
-        var openHandler = this.dialogElement("openHandler");
-        var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
-        if (!lastSelectedID)
-            lastSelectedID = "defaultHandler";
-        openHandler.selectedItem = this.dialogElement(lastSelectedID);
-    }
-
-#else
-      var nsIFilePicker = Components.interfaces.nsIFilePicker;
-      var fp = Components.classes["@mozilla.org/filepicker;1"]
-                         .createInstance(nsIFilePicker);
-      fp.init(this.mDialog,
-              this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
-              nsIFilePicker.modeOpen);
-
-      fp.appendFilters(nsIFilePicker.filterApps);
-
-      if (fp.show() == nsIFilePicker.returnOK && fp.file) {
-        // Show the "handler" menulist since we have a (user-specified) 
-        // application now.
-        this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");
-        
-        // Remember the file they chose to run.
-        var localHandlerApp = 
-          Components.classes["@mozilla.org/uriloader/local-handler-app;1"].
-          createInstance(Components.interfaces.nsILocalHandlerApp);
-        localHandlerApp.executable = fp.file;
-        this.chosenApp = localHandlerApp;
-        
-        // Update dialog.
-        var otherHandler = this.dialogElement("otherHandler");
-        otherHandler.removeAttribute("hidden");
-        otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable));
-#ifdef XP_MACOSX
-        this.chosenApp.executable
-            .QueryInterface(Components.interfaces.nsILocalFileMac);
-        otherHandler.label = this.chosenApp.executable.bundleDisplayName;
-#else
-        otherHandler.label = this.chosenApp.executable.leafName;
-#endif
-        this.dialogElement("openHandler").selectedIndex = 1;
-        this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler");
-        
-        this.dialogElement("mode").selectedItem = this.dialogElement("open");
-      }
-      else {
-        var openHandler = this.dialogElement("openHandler");
-        var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
-        if (!lastSelectedID)
-          lastSelectedID = "defaultHandler";
-        openHandler.selectedItem = this.dialogElement(lastSelectedID);
-      }
-#endif
-    },
-
-    // Turn this on to get debugging messages.
-    debug: false,
-
-    // Dump text (if debug is on).
-    dump: function( text ) {
-        if ( this.debug ) {
-            dump( text ); 
-        }
-    },
-
-    // dumpInfo:
-    doDebug: function() {
-        const nsIProgressDialog = Components.interfaces.nsIProgressDialog;
-        // Open new progress dialog.
-        var progress = Components.classes[ "@mozilla.org/progressdialog;1" ]
-                         .createInstance( nsIProgressDialog );
-        // Show it.
-        progress.open( this.mDialog );
-    },
-
-    // dumpObj:
-    dumpObj: function( spec ) {
-         var val = "<undefined>";
-         try {
-             val = eval( "this."+spec ).toString();
-         } catch( exception ) {
-         }
-         this.dump( spec + "=" + val + "\n" );
-    },
-
-    // dumpObjectProperties
-    dumpObjectProperties: function( desc, obj ) {
-         for( prop in obj ) {
-             this.dump( desc + "." + prop + "=" );
-             var val = "<undefined>";
-             try {
-                 val = obj[ prop ];
-             } catch ( exception ) {
-             }
-             this.dump( val + "\n" );
-         }
-    }
-}
-
-// This Component's module implementation.  All the code below is used to get this
-// component registered and accessible via XPCOM.
-var module = {
-    // registerSelf: Register this component.
-    registerSelf: function (compMgr, fileSpec, location, type) {
-        compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
-
-        compMgr.registerFactoryLocation( this.cid,
-                                         "Unknown Content Type Dialog",
-                                         this.contractId,
-                                         fileSpec,
-                                         location,
-                                         type );
-    },
-
-    // getClassObject: Return this component's factory object.
-    getClassObject: function (compMgr, cid, iid) {
-        if (!cid.equals(this.cid)) {
-            throw Components.results.NS_ERROR_NO_INTERFACE;
-        }
-
-        if (!iid.equals(Components.interfaces.nsIFactory)) {
-            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-        }
-
-        return this.factory;
-    },
-
-    /* CID for this class */
-    cid: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),
-
-    /* Contract ID for this class */
-    contractId: "@mozilla.org/helperapplauncherdialog;1",
-
-    /* factory object */
-    factory: {
-        // createInstance: Return a new nsProgressDialog object.
-        createInstance: function (outer, iid) {
-            if (outer != null)
-                throw Components.results.NS_ERROR_NO_AGGREGATION;
-
-            return (new nsUnknownContentTypeDialog()).QueryInterface(iid);
-        }
-    },
-
-    // canUnload: n/a (returns true)
-    canUnload: function(compMgr) {
-        return true;
-    }
-};
-
-// NSGetModule: Return the nsIModule object.
-function NSGetModule(compMgr, fileSpec) {
-    return module;
-}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -0,0 +1,357 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Dao Gottwald <dao@mozilla.com>.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var EXPORTED_SYMBOLS = ["LightweightThemeManager"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const MAX_USED_THEMES_COUNT = 8;
+
+const MAX_PREVIEW_SECONDS = 30;
+
+const MANDATORY = ["id", "name", "headerURL"];
+const OPTIONAL = ["footerURL", "textcolor", "accentcolor", "iconURL",
+                  "previewURL", "author", "description", "homepageURL",
+                  "updateURL", "version"];
+
+const PERSIST_ENABLED = true;
+const PERSIST_BYPASS_CACHE = false;
+const PERSIST_FILES = {
+  headerURL: "lightweighttheme-header",
+  footerURL: "lightweighttheme-footer"
+};
+
+__defineGetter__("_prefs", function () {
+  delete this._prefs;
+  return this._prefs =
+         Cc["@mozilla.org/preferences-service;1"]
+           .getService(Ci.nsIPrefService).getBranch("lightweightThemes.");
+});
+
+__defineGetter__("_observerService", function () {
+  delete this._observerService;
+  return this._observerService =
+         Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+});
+
+__defineGetter__("_ioService", function () {
+  delete this._ioService;
+  return this._ioService =
+         Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+});
+
+var LightweightThemeManager = {
+  get usedThemes () {
+    try {
+      return JSON.parse(_prefs.getCharPref("usedThemes"));
+    } catch (e) {
+      return [];
+    }
+  },
+
+  get currentTheme () {
+    try {
+      if (_prefs.getBoolPref("isThemeSelected"))
+        var data = this.usedThemes[0];
+    } catch (e) {}
+
+    return data || null;
+  },
+
+  get currentThemeForDisplay () {
+    var data = this.currentTheme;
+
+    if (data && PERSIST_ENABLED) {
+      for (let key in PERSIST_FILES) {
+        try {
+          if (data[key] && _prefs.getBoolPref("persisted." + key))
+            data[key] = _getLocalImageURI(PERSIST_FILES[key]).spec
+                        + "?" + data.id + ";" + _version(data);
+        } catch (e) {}
+      }
+    }
+
+    return data;
+  },
+
+  set currentTheme (aData) {
+    let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+    cancel.data = false;
+    _observerService.notifyObservers(cancel, "lightweight-theme-change-requested",
+                                     JSON.stringify(aData));
+
+    if (aData) {
+      let usedThemes = _usedThemesExceptId(aData.id);
+      if (cancel.data && _prefs.getBoolPref("isThemeSelected"))
+        usedThemes.splice(1, 0, aData);
+      else
+        usedThemes.unshift(aData);
+      _updateUsedThemes(usedThemes);
+    }
+
+    if (cancel.data)
+      return null;
+
+    if (_previewTimer) {
+      _previewTimer.cancel();
+      _previewTimer = null;
+    }
+
+    _prefs.setBoolPref("isThemeSelected", aData != null);
+    _notifyWindows(aData);
+    _observerService.notifyObservers(null, "lightweight-theme-changed", null);
+
+    if (PERSIST_ENABLED && aData)
+      _persistImages(aData);
+
+    return aData;
+  },
+
+  getUsedTheme: function (aId) {
+    var usedThemes = this.usedThemes;
+    for (let i = 0; i < usedThemes.length; i++) {
+      if (usedThemes[i].id == aId)
+        return usedThemes[i];
+    }
+    return null;
+  },
+
+  forgetUsedTheme: function (aId) {
+    var currentTheme = this.currentTheme;
+    if (currentTheme && currentTheme.id == aId)
+      this.currentTheme = null;
+
+    _updateUsedThemes(_usedThemesExceptId(aId));
+  },
+
+  previewTheme: function (aData) {
+    if (!aData)
+      return;
+
+    let cancel = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+    cancel.data = false;
+    _observerService.notifyObservers(cancel, "lightweight-theme-preview-requested",
+                                     JSON.stringify(aData));
+    if (cancel.data)
+      return;
+
+    if (_previewTimer)
+      _previewTimer.cancel();
+    else
+      _previewTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    _previewTimer.initWithCallback(_previewTimerCallback,
+                                   MAX_PREVIEW_SECONDS * 1000,
+                                   _previewTimer.TYPE_ONE_SHOT);
+
+    _notifyWindows(aData);
+  },
+
+  resetPreview: function () {
+    if (_previewTimer) {
+      _previewTimer.cancel();
+      _previewTimer = null;
+      _notifyWindows(this.currentThemeForDisplay);
+    }
+  },
+
+  parseTheme: function (aString, aBaseURI) {
+    try {
+      var data = JSON.parse(aString);
+    } catch (e) {
+      return null;
+    }
+
+    if (!data || typeof data != "object")
+      return null;
+
+    for (let prop in data) {
+      if (typeof data[prop] == "string" &&
+          (data[prop] = data[prop].trim()) &&
+          (MANDATORY.indexOf(prop) > -1 || OPTIONAL.indexOf(prop) > -1)) {
+        if (!/URL$/.test(prop))
+          continue;
+
+        try {
+          data[prop] = _makeURI(data[prop], _makeURI(aBaseURI)).spec;
+          if (/^https:/.test(data[prop]))
+            continue;
+          if (prop != "updateURL" && /^http:/.test(data[prop]))
+            continue;
+        } catch (e) {}
+      }
+
+      delete data[prop];
+    }
+
+    for (let i = 0; i < MANDATORY.length; i++) {
+      if (!(MANDATORY[i] in data))
+        return null;
+    }
+
+    return data;
+  },
+
+  updateCurrentTheme: function () {
+    try {
+      if (!_prefs.getBoolPref("update.enabled"))
+        return;
+    } catch (e) {
+      return;
+    }
+
+    var theme = this.currentTheme;
+    if (!theme || !theme.updateURL)
+      return;
+
+    var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                .createInstance(Ci.nsIXMLHttpRequest);
+
+    req.mozBackgroundRequest = true;
+    req.overrideMimeType("text/plain");
+    req.open("GET", theme.updateURL, true);
+
+    var self = this;
+    req.onload = function () {
+      if (req.status != 200)
+        return;
+
+      let newData = self.parseTheme(req.responseText, theme.updateURL);
+      if (!newData ||
+          newData.id != theme.id ||
+          _version(newData) == _version(theme))
+        return;
+
+      var currentTheme = self.currentTheme;
+      if (currentTheme && currentTheme.id == theme.id)
+        self.currentTheme = newData;
+    };
+
+    req.send(null);
+  }
+};
+
+function _usedThemesExceptId(aId)
+  LightweightThemeManager.usedThemes.filter(function (t) t.id != aId);
+
+function _version(aThemeData)
+  aThemeData.version || "";
+
+function _makeURI(aURL, aBaseURI)
+  _ioService.newURI(aURL, null, aBaseURI);
+
+function _updateUsedThemes(aList) {
+  if (aList.length > MAX_USED_THEMES_COUNT)
+    aList.length = MAX_USED_THEMES_COUNT;
+
+  _prefs.setCharPref("usedThemes", JSON.stringify(aList));
+
+  _observerService.notifyObservers(null, "lightweight-theme-list-changed", null);
+}
+
+function _notifyWindows(aThemeData) {
+  _observerService.notifyObservers(null, "lightweight-theme-styling-update",
+                                   JSON.stringify(aThemeData));
+}
+
+var _previewTimer;
+var _previewTimerCallback = {
+  notify: function () {
+    LightweightThemeManager.resetPreview();
+  }
+};
+
+function _persistImages(aData) {
+  function onSuccess(key) function () {
+    let current = LightweightThemeManager.currentTheme;
+    if (current && current.id == aData.id)
+      _prefs.setBoolPref("persisted." + key, true);
+  };
+
+  for (let key in PERSIST_FILES) {
+    _prefs.setBoolPref("persisted." + key, false);
+    if (aData[key])
+      _persistImage(aData[key], PERSIST_FILES[key], onSuccess(key));
+  }
+}
+
+function _getLocalImageURI(localFileName) {
+  var localFile = Cc["@mozilla.org/file/directory_service;1"]
+                    .getService(Ci.nsIProperties)
+                    .get("ProfD", Ci.nsILocalFile);
+  localFile.append(localFileName);
+  return _ioService.newFileURI(localFile);
+}
+
+function _persistImage(sourceURL, localFileName, callback) {
+  var targetURI = _getLocalImageURI(localFileName);
+  var sourceURI = _makeURI(sourceURL);
+
+  var persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+                  .createInstance(Ci.nsIWebBrowserPersist);
+
+  persist.persistFlags =
+    Ci.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+    Ci.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION |
+    (PERSIST_BYPASS_CACHE ?
+       Ci.nsIWebBrowserPersist.PERSIST_FLAGS_BYPASS_CACHE :
+       Ci.nsIWebBrowserPersist.PERSIST_FLAGS_FROM_CACHE);
+
+  persist.progressListener = new _persistProgressListener(callback);
+
+  persist.saveURI(sourceURI, null, null, null, null, targetURI);
+}
+
+function _persistProgressListener(callback) {
+  this.onLocationChange = function () {};
+  this.onProgressChange = function () {};
+  this.onStatusChange   = function () {};
+  this.onSecurityChange = function () {};
+  this.onStateChange    = function (aWebProgress, aRequest, aStateFlags, aStatus) {
+    if (aRequest &&
+        aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+        aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+      try {
+        if (aRequest.QueryInterface(Ci.nsIHttpChannel).requestSucceeded) {
+          // success
+          callback();
+          return;
+        }
+      } catch (e) { }
+      // failure
+    }
+  };
+}
--- a/toolkit/mozapps/extensions/Makefile.in
+++ b/toolkit/mozapps/extensions/Makefile.in
@@ -29,22 +29,42 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
-DEPTH		= ../../..
-topsrcdir	= @top_srcdir@
-srcdir		= @srcdir@
-VPATH		= @srcdir@
+DEPTH     = ../../..
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-DIRS = public src
+MODULE = extensions
+
+XPIDLSRCS = \
+  nsIAddonRepository.idl \
+  nsIExtensionManager.idl \
+  $(NULL)
+
+EXTRA_COMPONENTS = nsExtensionManager.js
+GARBAGE += nsExtensionManager.js
+
+EXTRA_PP_COMPONENTS = \
+  nsAddonRepository.js \
+  nsBlocklistService.js \
+  $(NULL)
+
+EXTRA_JS_MODULES = \
+  LightweightThemeManager.jsm \
+  $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
 include $(topsrcdir)/config/rules.mk
+
+nsExtensionManager.js: nsExtensionManager.js.in
+	$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $^ > $@ 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/nsAddonRepository.js
@@ -0,0 +1,383 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Extension Manager.
+#
+# The Initial Developer of the Original Code is mozilla.org
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Dave Townsend <dtownsend@oxymoronical.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+*/
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PREF_GETADDONS_BROWSEADDONS        = "extensions.getAddons.browseAddons";
+const PREF_GETADDONS_BROWSERECOMMENDED   = "extensions.getAddons.recommended.browseURL";
+const PREF_GETADDONS_GETRECOMMENDED      = "extensions.getAddons.recommended.url";
+const PREF_GETADDONS_BROWSESEARCHRESULTS = "extensions.getAddons.search.browseURL";
+const PREF_GETADDONS_GETSEARCHRESULTS    = "extensions.getAddons.search.url";
+
+const XMLURI_PARSE_ERROR  = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
+
+const API_VERSION = "1.2";
+
+function AddonSearchResult() {
+}
+
+AddonSearchResult.prototype = {
+  id: null,
+  name: null,
+  version: null,
+  summary: null,
+  description: null,
+  rating: null,
+  iconURL: null,
+  thumbnailURL: null,
+  homepageURL: null,
+  eula: null,
+  type: null,
+  xpiURL: null,
+  xpiHash: null,
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonSearchResult])
+}
+
+function AddonRepository() {
+}
+
+AddonRepository.prototype = {
+  // The current set of results
+  _addons: null,
+
+  // Whether we are currently searching or not
+  _searching: false,
+
+  // Is this a search for recommended add-ons
+  _recommended: false,
+
+  // XHR associated with the current request
+  _request: null,
+
+  // Callback object to notify on completion
+  _callback: null,
+
+  // Maximum number of results to return
+  _maxResults: null,
+
+  get homepageURL() {
+    return Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                     .getService(Components.interfaces.nsIURLFormatter)
+                     .formatURLPref(PREF_GETADDONS_BROWSEADDONS);
+  },
+
+  get isSearching() {
+    return this._searching;
+  },
+
+  getRecommendedURL: function() {
+    var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                         .getService(Components.interfaces.nsIURLFormatter);
+
+    return urlf.formatURLPref(PREF_GETADDONS_BROWSERECOMMENDED);
+  },
+
+  getSearchURL: function(aSearchTerms) {
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch);
+    var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                         .getService(Components.interfaces.nsIURLFormatter);
+
+    var url = prefs.getCharPref(PREF_GETADDONS_BROWSESEARCHRESULTS);
+    url = url.replace(/%TERMS%/g, encodeURIComponent(aSearchTerms));
+    return urlf.formatURL(url);
+  },
+
+  cancelSearch: function() {
+    this._searching = false;
+    if (this._request) {
+      this._request.abort();
+      this._request = null;
+    }
+    this._callback = null;
+    this._addons = null;
+  },
+
+  retrieveRecommendedAddons: function(aMaxResults, aCallback) {
+    if (this._searching)
+      return;
+
+    this._searching = true;
+    this._addons = [];
+    this._callback = aCallback;
+    this._recommended = true;
+    this._maxResults = aMaxResults;
+
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch);
+    var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                         .getService(Components.interfaces.nsIURLFormatter);
+
+    var uri = prefs.getCharPref(PREF_GETADDONS_GETRECOMMENDED);
+    uri = uri.replace(/%API_VERSION%/g, API_VERSION);
+    uri = urlf.formatURL(uri);
+    this._loadList(uri);
+  },
+
+  searchAddons: function(aSearchTerms, aMaxResults, aCallback) {
+    if (this._searching)
+      return;
+
+    this._searching = true;
+    this._addons = [];
+    this._callback = aCallback;
+    this._recommended = false;
+    this._maxResults = aMaxResults;
+
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch);
+    var urlf = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                         .getService(Components.interfaces.nsIURLFormatter);
+
+    var uri = prefs.getCharPref(PREF_GETADDONS_GETSEARCHRESULTS);
+    uri = uri.replace(/%API_VERSION%/g, API_VERSION);
+    // We double encode due to bug 427155
+    uri = uri.replace(/%TERMS%/g, encodeURIComponent(encodeURIComponent(aSearchTerms)));
+    uri = urlf.formatURL(uri);
+    this._loadList(uri);
+  },
+
+  // Posts results to the callback
+  _reportSuccess: function(aCount) {
+    this._searching = false;
+    this._request = null;
+    // The callback may want to trigger a new search so clear references early
+    var addons = this._addons;
+    var callback = this._callback;
+    this._callback = null;
+    this._addons = null;
+    callback.searchSucceeded(addons, addons.length, this._recommended ? -1 : aCount);
+  },
+
+  // Notifies the callback of a failure
+  _reportFailure: function(aEvent) {
+    this._searching = false;
+    this._request = null;
+    // The callback may want to trigger a new search so clear references early
+    var callback = this._callback;
+    this._callback = null;
+    this._addons = null;
+    callback.searchFailed();
+  },
+
+  // Parses an add-on entry from an <addon> element
+  _parseAddon: function(element) {
+    var em = Cc["@mozilla.org/extensions/manager;1"].
+             getService(Ci.nsIExtensionManager);
+    var app = Cc["@mozilla.org/xre/app-info;1"].
+              getService(Ci.nsIXULAppInfo).
+              QueryInterface(Ci.nsIXULRuntime);
+
+    var guid = element.getElementsByTagName("guid");
+    if (guid.length != 1)
+      return;
+
+    // Ignore add-ons already seen in the results
+    for (var i = 0; i < this._addons.length; i++)
+      if (this._addons[i].id == guid[0].textContent)
+        return;
+
+    // Ignore installed add-ons
+    if (em.getItemForID(guid[0].textContent) != null)
+      return;
+
+    // Ignore sandboxed add-ons
+    var status = element.getElementsByTagName("status");
+    // The status element has a unique id for each status type. 4 is Public.
+    if (status.length != 1 || status[0].getAttribute("id") != 4)
+      return;
+
+    // Ignore add-ons not compatible with this OS
+    var os = element.getElementsByTagName("compatible_os");
+    // Only the version 0 schema included compatible_os if it isn't there then
+    // we will see os compatibility on the install elements.
+    if (os.length > 0) {
+      var compatible = false;
+      var i = 0;
+      while (i < os.length && !compatible) {
+        if (os[i].textContent == "ALL" || os[i].textContent == app.OS) {
+          compatible = true;
+          break;
+        }
+        i++;
+      }
+      if (!compatible)
+        return;
+    }
+
+    // Ignore add-ons not compatible with this Application
+    compatible = false;
+    var tags = element.getElementsByTagName("compatible_applications");
+    if (tags.length != 1)
+      return;
+    var vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
+             getService(Ci.nsIVersionComparator);
+    var apps = tags[0].getElementsByTagName("appID");
+    var i = 0;
+    while (i < apps.length) {
+      if (apps[i].textContent == app.ID) {
+        var minversion = apps[i].parentNode.getElementsByTagName("min_version")[0].textContent;
+        var maxversion = apps[i].parentNode.getElementsByTagName("max_version")[0].textContent;
+        if ((vc.compare(minversion, app.version) > 0) ||
+            (vc.compare(app.version, maxversion) > 0))
+          return;
+        compatible = true;
+        break;
+      }
+      i++;
+    }
+    if (!compatible)
+      return;
+
+    var addon = new AddonSearchResult();
+    addon.id = guid[0].textContent;
+    addon.rating = -1;
+    var node = element.firstChild;
+    while (node) {
+      if (node instanceof Ci.nsIDOMElement) {
+        switch (node.localName) {
+          case "name":
+          case "version":
+          case "summary":
+          case "description":
+          case "eula":
+            addon[node.localName] = node.textContent;
+            break;
+          case "rating":
+            if (node.textContent.length > 0) {
+              var rating = parseInt(node.textContent);
+              if (rating >= 0)
+                addon.rating = Math.min(5, rating);
+            }
+            break;
+          case "thumbnail":
+            addon.thumbnailURL = node.textContent;
+            break;
+          case "icon":
+            addon.iconURL = node.textContent;
+            break;
+          case "learnmore":
+            addon.homepageURL = node.textContent;
+            break;
+          case "type":
+            // The type element has an id attribute that is the id from AMO's
+            // database. This doesn't match our type values to perform a mapping
+            if (node.getAttribute("id") == 2)
+              addon.type = Ci.nsIUpdateItem.TYPE_THEME;
+            else
+              addon.type = Ci.nsIUpdateItem.TYPE_EXTENSION;
+            break;
+          case "install":
+            // No os attribute means the xpi is compatible with any os
+            if (node.hasAttribute("os")) {
+              var os = node.getAttribute("os").toLowerCase();
+              // If the os is not ALL and not the current OS then ignore this xpi
+              if (os != "all" && os != app.OS.toLowerCase())
+                break;
+            }
+            addon.xpiURL = node.textContent;
+            if (node.hasAttribute("hash"))
+              addon.xpiHash = node.getAttribute("hash");
+            break;
+        }
+      }
+      node = node.nextSibling;
+    }
+
+    // Add only if there was an xpi compatible with this os
+    if (addon.xpiURL)
+      this._addons.push(addon);
+  },
+
+  // Called when a single request has completed, parses out any add-ons and
+  // either notifies the callback or does a new request for more results
+  _listLoaded: function(aEvent) {
+    var request = aEvent.target;
+    var responseXML = request.responseXML;
+
+    if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
+        (request.status != 200 && request.status != 0)) {
+      this._reportFailure();
+      return;
+    }
+    var elements = responseXML.documentElement.getElementsByTagName("addon");
+    for (var i = 0; i < elements.length; i++) {
+      this._parseAddon(elements[i]);
+
+      var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefBranch);
+      if (this._addons.length == this._maxResults) {
+        this._reportSuccess(elements.length);
+        return;
+      }
+    }
+
+    if (responseXML.documentElement.hasAttribute("total_results"))
+      this._reportSuccess(responseXML.documentElement.getAttribute("total_results"));
+    else
+      this._reportSuccess(elements.length);
+  },
+
+  // Performs a new request for results
+  _loadList: function(aURI) {
+    this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+                    createInstance(Ci.nsIXMLHttpRequest);
+    this._request.open("GET", aURI, true);
+    this._request.overrideMimeType("text/xml");
+
+    var self = this;
+    this._request.onerror = function(event) { self._reportFailure(event); };
+    this._request.onload = function(event) { self._listLoaded(event); };
+    this._request.send(null);
+  },
+
+  classDescription: "Addon Repository",
+  contractID: "@mozilla.org/extensions/addon-repository;1",
+  classID: Components.ID("{8eaaf524-7d6d-4f7d-ae8b-9277b324008d}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonRepository])
+}
+
+function NSGetModule(aCompMgr, aFileSpec) {
+  return XPCOMUtils.generateModule([AddonRepository]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -0,0 +1,1045 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Blocklist Service.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2007
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Strong <robert.bugzilla@gmail.com>
+#   Michael Wu <flamingice@sourmilk.net>
+#   Dave Townsend <dtownsend@oxymoronical.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+*/
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+const TOOLKIT_ID                      = "toolkit@mozilla.org"
+const KEY_PROFILEDIR                  = "ProfD";
+const KEY_APPDIR                      = "XCurProcD";
+const FILE_BLOCKLIST                  = "blocklist.xml";
+const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
+const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
+const PREF_BLOCKLIST_INTERVAL         = "extensions.blocklist.interval";
+const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
+const PREF_PLUGINS_NOTIFYUSER         = "plugins.update.notifyUser";
+const PREF_GENERAL_USERAGENT_LOCALE   = "general.useragent.locale";
+const PREF_PARTNER_BRANCH             = "app.partner.";
+const PREF_APP_DISTRIBUTION           = "distribution.id";
+const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
+const PREF_APP_UPDATE_CHANNEL         = "app.update.channel";
+const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
+const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
+const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
+const UNKNOWN_XPCOM_ABI               = "unknownABI";
+const URI_BLOCKLIST_DIALOG            = "chrome://mozapps/content/extensions/blocklist.xul"
+const DEFAULT_SEVERITY                = 3;
+const DEFAULT_LEVEL                   = 2;
+const MAX_BLOCK_LEVEL                 = 3;
+const SEVERITY_OUTDATED               = 0;
+
+var gLoggingEnabled = null;
+var gBlocklistEnabled = true;
+var gBlocklistLevel = DEFAULT_LEVEL;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
+                                   "@mozilla.org/consoleservice;1",
+                                   "nsIConsoleService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gVersionChecker",
+                                   "@mozilla.org/xpcom/version-comparator;1",
+                                   "nsIVersionComparator");
+
+XPCOMUtils.defineLazyGetter(this, "gPref", function bls_gPref() {
+  return Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).
+         QueryInterface(Ci.nsIPrefBranch2);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gApp", function bls_gApp() {
+  return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).
+         QueryInterface(Ci.nsIXULRuntime);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gABI", function bls_gABI() {
+  let abi = null;
+  try {
+    abi = gApp.XPCOMABI;
+  }
+  catch (e) {
+    LOG("BlockList Global gABI: XPCOM ABI unknown.");
+  }
+#ifdef XP_MACOSX
+  // Mac universal build should report a different ABI than either macppc
+  // or mactel.
+  let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
+                 getService(Ci.nsIMacUtils);
+
+  if (macutils.isUniversalBinary)
+    abi = "Universal-gcc3";
+#endif
+  return abi;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gOSVersion", function bls_gOSVersion() {
+  let osVersion;
+  let sysInfo = Cc["@mozilla.org/system-info;1"].
+                getService(Ci.nsIPropertyBag2);
+  try {
+    osVersion = sysInfo.getProperty("name") + " " + sysInfo.getProperty("version");
+  }
+  catch (e) {
+    LOG("BlockList Global gOSVersion: OS Version unknown.");
+  }
+
+  if (osVersion) {
+    try {
+      osVersion += " (" + sysInfo.getProperty("secondaryLibrary") + ")";
+    }
+    catch (e) {
+      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
+    }
+    osVersion = encodeURIComponent(osVersion);
+  }
+  return osVersion;
+});
+
+// shared code for suppressing bad cert dialogs
+XPCOMUtils.defineLazyGetter(this, "gCertUtils", function bls_gCertUtils() {
+  let temp = { };
+  Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
+  return temp;
+});
+
+function getObserverService() {
+  return Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+}
+
+/**
+ * Logs a string to the error console.
+ * @param   string
+ *          The string to write to the error console..
+ */
+function LOG(string) {
+  if (gLoggingEnabled) {
+    dump("*** " + string + "\n");
+    gConsole.logStringMessage(string);
+  }
+}
+
+/**
+ * Gets a preference value, handling the case where there is no default.
+ * @param   func
+ *          The name of the preference function to call, on nsIPrefBranch
+ * @param   preference
+ *          The name of the preference
+ * @param   defaultValue
+ *          The default value to return in the event the preference has
+ *          no setting
+ * @returns The value of the preference, or undefined if there was no
+ *          user or default value.
+ */
+function getPref(func, preference, defaultValue) {
+  try {
+    return gPref[func](preference);
+  }
+  catch (e) {
+  }
+  return defaultValue;
+}
+
+/**
+ * Constructs a URI to a spec.
+ * @param   spec
+ *          The spec to construct a URI to
+ * @returns The nsIURI constructed.
+ */
+function newURI(spec) {
+  var ioServ = Cc["@mozilla.org/network/io-service;1"].
+               getService(Ci.nsIIOService);
+  return ioServ.newURI(spec, null, null);
+}
+
+// Restarts the application checking in with observers first
+function restartApp() {
+  // Notify all windows that an application quit has been requested.
+  var os = Cc["@mozilla.org/observer-service;1"].
+           getService(Ci.nsIObserverService);
+  var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+                   createInstance(Ci.nsISupportsPRBool);
+  os.notifyObservers(cancelQuit, "quit-application-requested", null);
+
+  // Something aborted the quit process. 
+  if (cancelQuit.data)
+    return;
+
+  var as = Cc["@mozilla.org/toolkit/app-startup;1"].
+           getService(Ci.nsIAppStartup);
+  as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+}
+
+/**
+ * Checks whether this blocklist element is valid for the current OS and ABI.
+ * If the element has an "os" attribute then the current OS must appear in
+ * its comma separated list for the element to be valid. Similarly for the
+ * xpcomabi attribute.
+ */
+function matchesOSABI(blocklistElement) {
+  if (blocklistElement.hasAttribute("os")) {
+    var choices = blocklistElement.getAttribute("os").split(",");
+    if (choices.length > 0 && choices.indexOf(gApp.OS) < 0)
+      return false;
+  }
+  
+  if (blocklistElement.hasAttribute("xpcomabi")) {
+    choices = blocklistElement.getAttribute("xpcomabi").split(",");
+    if (choices.length > 0 && choices.indexOf(gApp.XPCOMABI) < 0)
+      return false;
+  }
+  
+  return true;
+}
+
+/**
+ * Gets the current value of the locale.  It's possible for this preference to
+ * be localized, so we have to do a little extra work here.  Similar code
+ * exists in nsHttpHandler.cpp when building the UA string.
+ */
+function getLocale() {
+  try {
+      // Get the default branch
+      var defaultPrefs = gPref.getDefaultBranch(null);
+      return defaultPrefs.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
+  } catch (e) {}
+
+  return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
+}
+
+/**
+ * Read the update channel from defaults only.  We do this to ensure that
+ * the channel is tightly coupled with the application and does not apply
+ * to other installations of the application that may use the same profile.
+ */
+function getUpdateChannel() {
+  var channel = "default";
+  var prefName;
+  var prefValue;
+
+  var defaults = gPref.getDefaultBranch(null);
+  try {
+    channel = defaults.getCharPref(PREF_APP_UPDATE_CHANNEL);
+  } catch (e) {
+    // use default when pref not found
+  }
+
+  try {
+    var partners = gPref.getChildList(PREF_PARTNER_BRANCH);
+    if (partners.length) {
+      channel += "-cck";
+      partners.sort();
+
+      for each (prefName in partners) {
+        prefValue = gPref.getCharPref(prefName);
+        channel += "-" + prefValue;
+      }
+    }
+  }
+  catch (e) {
+    Components.utils.reportError(e);
+  }
+
+  return channel;
+}
+
+/* Get the distribution pref values, from defaults only */
+function getDistributionPrefValue(aPrefName) {
+  var prefValue = "default";
+
+  var defaults = gPref.getDefaultBranch(null);
+  try {
+    prefValue = defaults.getCharPref(aPrefName);
+  } catch (e) {
+    // use default when pref not found
+  }
+
+  return prefValue;
+}
+
+/**
+ * Manages the Blocklist. The Blocklist is a representation of the contents of
+ * blocklist.xml and allows us to remotely disable / re-enable blocklisted
+ * items managed by the Extension Manager with an item's appDisabled property.
+ * It also blocklists plugins with data from blocklist.xml.
+ */
+
+function Blocklist() {
+  let os = getObserverService();
+  os.addObserver(this, "xpcom-shutdown", false);
+  gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
+  gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
+  gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+                                     MAX_BLOCK_LEVEL);
+  gPref.addObserver("extensions.blocklist.", this, false);
+}
+
+Blocklist.prototype = {
+  /**
+   * Extension ID -> array of Version Ranges
+   * Each value in the version range array is a JS Object that has the
+   * following properties:
+   *   "minVersion"  The minimum version in a version range (default = 0)
+   *   "maxVersion"  The maximum version in a version range (default = *)
+   *   "targetApps"  Application ID -> array of Version Ranges
+   *                 (default = current application ID)
+   *                 Each value in the version range array is a JS Object that
+   *                 has the following properties:
+   *                   "minVersion"  The minimum version in a version range
+   *                                 (default = 0)
+   *                   "maxVersion"  The maximum version in a version range
+   *                                 (default = *)
+   */
+  _addonEntries: null,
+  _pluginEntries: null,
+
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+    case "xpcom-shutdown":
+      let os = getObserverService();
+      os.removeObserver(this, "xpcom-shutdown");
+      gPref.removeObserver("extensions.blocklist.", this);
+      break;
+    case "nsPref:changed":
+      switch (aData) {
+        case PREF_BLOCKLIST_ENABLED:
+          gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
+          this._loadBlocklist();
+          this._blocklistUpdated(null, null);
+          break;
+        case PREF_BLOCKLIST_LEVEL:
+          gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+                                     MAX_BLOCK_LEVEL);
+          this._blocklistUpdated(null, null);
+          break;
+      }
+      break;
+    }
+  },
+
+  /* See nsIBlocklistService */
+  isAddonBlocklisted: function(id, version, appVersion, toolkitVersion) {
+    return this.getAddonBlocklistState(id, version, appVersion, toolkitVersion) ==
+                   Ci.nsIBlocklistService.STATE_BLOCKED;
+  },
+
+  /* See nsIBlocklistService */
+  getAddonBlocklistState: function(id, version, appVersion, toolkitVersion) {
+    if (!this._addonEntries)
+      this._loadBlocklist();
+    return this._getAddonBlocklistState(id, version, this._addonEntries,
+                                        appVersion, toolkitVersion);
+  },
+
+  /**
+   * Private version of getAddonBlocklistState that allows the caller to pass in
+   * the add-on blocklist entries to compare against.
+   *
+   * @param   id
+   *          The ID of the item to get the blocklist state for.
+   * @param   version
+   *          The version of the item to get the blocklist state for.
+   * @param   addonEntries
+   *          The add-on blocklist entries to compare against.
+   * @param   appVersion
+   *          The application version to compare to, will use the current
+   *          version if null.
+   * @param   toolkitVersion
+   *          The toolkit version to compare to, will use the current version if
+   *          null.
+   * @returns The blocklist state for the item, one of the STATE constants as
+   *          defined in nsIBlocklistService.
+   */
+  _getAddonBlocklistState: function(id, version, addonEntries, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled)
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+    if (!appVersion)
+      appVersion = gApp.version;
+    if (!toolkitVersion)
+      toolkitVersion = gApp.platformVersion;
+
+    var blItem = addonEntries[id];
+    if (!blItem)
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+    for (var i = 0; i < blItem.length; ++i) {
+      if (blItem[i].includesItem(version, appVersion, toolkitVersion))
+        return blItem[i].severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
+                                                       Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+    }
+    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+  },
+
+  notify: function(aTimer) {
+    if (!gBlocklistEnabled)
+      return;
+
+    try {
+      var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
+    }
+    catch (e) {
+      LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
+          " is missing!");
+      return;
+    }
+
+    dsURI = dsURI.replace(/%APP_ID%/g, gApp.ID);
+    dsURI = dsURI.replace(/%APP_VERSION%/g, gApp.version);
+    dsURI = dsURI.replace(/%PRODUCT%/g, gApp.name);
+    dsURI = dsURI.replace(/%VERSION%/g, gApp.version);
+    dsURI = dsURI.replace(/%BUILD_ID%/g, gApp.appBuildID);
+    dsURI = dsURI.replace(/%BUILD_TARGET%/g, gApp.OS + "_" + gABI);
+    dsURI = dsURI.replace(/%OS_VERSION%/g, gOSVersion);
+    dsURI = dsURI.replace(/%LOCALE%/g, getLocale());
+    dsURI = dsURI.replace(/%CHANNEL%/g, getUpdateChannel());
+    dsURI = dsURI.replace(/%PLATFORM_VERSION%/g, gApp.platformVersion);
+    dsURI = dsURI.replace(/%DISTRIBUTION%/g,
+                      getDistributionPrefValue(PREF_APP_DISTRIBUTION));
+    dsURI = dsURI.replace(/%DISTRIBUTION_VERSION%/g,
+                      getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
+    dsURI = dsURI.replace(/\+/g, "%2B");
+
+    // Verify that the URI is valid
+    try {
+      var uri = newURI(dsURI);
+    }
+    catch (e) {
+      LOG("Blocklist::notify: There was an error creating the blocklist URI\r\n" +
+          "for: " + dsURI + ", error: " + e);
+      return;
+    }
+
+    var request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
+                  createInstance(Ci.nsIXMLHttpRequest);
+    request.open("GET", uri.spec, true);
+    request.channel.notificationCallbacks = new gCertUtils.BadCertHandler();
+    request.overrideMimeType("text/xml");
+    request.setRequestHeader("Cache-Control", "no-cache");
+    request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
+
+    var self = this;
+    request.onerror = function(event) { self.onXMLError(event); };
+    request.onload  = function(event) { self.onXMLLoad(event);  };
+    request.send(null);
+
+    // When the blocklist loads we need to compare it to the current copy so
+    // make sure we have loaded it.
+    if (!this._addonEntries)
+      this._loadBlocklist();
+  },
+
+  onXMLLoad: function(aEvent) {
+    var request = aEvent.target;
+    try {
+      gCertUtils.checkCert(request.channel);
+    }
+    catch (e) {
+      LOG("Blocklist::onXMLLoad: " + e);
+      return;
+    }
+    var responseXML = request.responseXML;
+    if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
+        (request.status != 200 && request.status != 0)) {
+      LOG("Blocklist::onXMLLoad: there was an error during load");
+      return;
+    }
+    var blocklistFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+    if (blocklistFile.exists())
+      blocklistFile.remove(false);
+    var fos = FileUtils.openSafeFileOutputStream(blocklistFile);
+    fos.write(request.responseText, request.responseText.length);
+    FileUtils.closeSafeFileOutputStream(fos);
+
+    var oldAddonEntries = this._addonEntries;
+    var oldPluginEntries = this._pluginEntries;
+    this._addonEntries = { };
+    this._pluginEntries = { };
+    this._loadBlocklistFromFile(FileUtils.getFile(KEY_PROFILEDIR,
+                                                  [FILE_BLOCKLIST]));
+
+    this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
+  },
+
+  onXMLError: function(aEvent) {
+    try {
+      var request = aEvent.target;
+      // the following may throw (e.g. a local file or timeout)
+      var status = request.status;
+    }
+    catch (e) {
+      request = aEvent.target.channel.QueryInterface(Ci.nsIRequest);
+      status = request.status;
+    }
+    var statusText = request.statusText;
+    // When status is 0 we don't have a valid channel.
+    if (status == 0)
+      statusText = "nsIXMLHttpRequest channel unavailable";
+    LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
+        statusText);
+  },
+
+  /**
+   * Finds the newest blocklist file from the application and the profile and
+   * load it or does nothing if neither exist.
+   */
+  _loadBlocklist: function() {
+    this._addonEntries = { };
+    this._pluginEntries = { };
+    var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+    if (profFile.exists()) {
+      this._loadBlocklistFromFile(profFile);
+      return;
+    }
+    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+    if (appFile.exists()) {
+      this._loadBlocklistFromFile(appFile);
+      return;
+    }
+    LOG("Blocklist::_loadBlocklist: no XML File found");
+  },
+
+  /**
+#    The blocklist XML file looks something like this:
+#
+#    <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+#      <emItems>
+#        <emItem id="item_1@domain">
+#          <versionRange minVersion="1.0" maxVersion="2.0.*">
+#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+#              <versionRange minVersion="1.7" maxVersion="1.7.*"/>
+#            </targetApplication>
+#            <targetApplication id="toolkit@mozilla.org">
+#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
+#            </targetApplication>
+#          </versionRange>
+#          <versionRange minVersion="3.0" maxVersion="3.0.*">
+#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+#            </targetApplication>
+#            <targetApplication id="toolkit@mozilla.org">
+#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
+#            </targetApplication>
+#          </versionRange>
+#        </emItem>
+#        <emItem id="item_2@domain">
+#          <versionRange minVersion="3.1" maxVersion="4.*"/>
+#        </emItem>
+#        <emItem id="item_3@domain">
+#          <versionRange>
+#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
+#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+#            </targetApplication>
+#          </versionRange>
+#        </emItem>
+#        <emItem id="item_4@domain">
+#          <versionRange>
+#            <targetApplication>
+#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
+#            </targetApplication>
+#          </versionRange>
+#        <emItem id="item_5@domain"/>
+#      </emItems>
+#      <pluginItems>
+#        <pluginItem>
+#          <!-- All match tags must match a plugin to blocklist a plugin -->
+#          <match name="name" exp="some plugin"/>
+#          <match name="description" exp="1[.]2[.]3"/>
+#        </pluginItem>
+#      </pluginItems>
+#    </blocklist>
+   */
+
+  _loadBlocklistFromFile: function(file) {
+    if (!gBlocklistEnabled) {
+      LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
+      return;
+    }
+
+    if (!file.exists()) {
+      LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist");
+      return;
+    }
+
+    var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+                               .createInstance(Components.interfaces.nsIFileInputStream);
+    fileStream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+    try {
+      var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+                   createInstance(Ci.nsIDOMParser);
+      var doc = parser.parseFromStream(fileStream, "UTF-8", file.fileSize, "text/xml");
+      if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
+        LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
+            "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
+            "Received: " + doc.documentElement.namespaceURI);
+        return;
+      }
+
+      var childNodes = doc.documentElement.childNodes;
+      this._addonEntries = this._processItemNodes(childNodes, "em",
+                                                  this._handleEmItemNode);
+      this._pluginEntries = this._processItemNodes(childNodes, "plugin",
+                                                   this._handlePluginItemNode);
+    }
+    catch (e) {
+      LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
+      return;
+    }
+    fileStream.close();
+  },
+
+  _processItemNodes: function(deChildNodes, prefix, handler) {
+    var result = [];
+    var itemNodes;
+    var containerName = prefix + "Items";
+    for (var i = 0; i < deChildNodes.length; ++i) {
+      var emItemsElement = deChildNodes.item(i);
+      if (emItemsElement instanceof Ci.nsIDOMElement &&
+          emItemsElement.localName == containerName) {
+        itemNodes = emItemsElement.childNodes;
+        break;
+      }
+    }
+    if (!itemNodes)
+      return result;
+
+    var itemName = prefix + "Item";
+    for (var i = 0; i < itemNodes.length; ++i) {
+      var blocklistElement = itemNodes.item(i);
+      if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
+          blocklistElement.localName != itemName)
+        continue;
+
+      handler(blocklistElement, result);
+    }
+    return result;
+  },
+
+  _handleEmItemNode: function(blocklistElement, result) {
+    if (!matchesOSABI(blocklistElement))
+      return;
+
+    var versionNodes = blocklistElement.childNodes;
+    var id = blocklistElement.getAttribute("id");
+    result[id] = [];
+    for (var x = 0; x < versionNodes.length; ++x) {
+      var versionRangeElement = versionNodes.item(x);
+      if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
+          versionRangeElement.localName != "versionRange")
+        continue;
+
+      result[id].push(new BlocklistItemData(versionRangeElement));
+    }
+    // if only the extension ID is specified block all versions of the
+    // extension for the current application.
+    if (result[id].length == 0)
+      result[id].push(new BlocklistItemData(null));
+  },
+
+  _handlePluginItemNode: function(blocklistElement, result) {
+    if (!matchesOSABI(blocklistElement))
+      return;
+
+    var matchNodes = blocklistElement.childNodes;
+    var blockEntry = {
+      matches: {},
+      versions: []
+    };
+    var hasMatch = false;
+    for (var x = 0; x < matchNodes.length; ++x) {
+      var matchElement = matchNodes.item(x);
+      if (!(matchElement instanceof Ci.nsIDOMElement))
+        continue;
+      if (matchElement.localName == "match") {
+        var name = matchElement.getAttribute("name");
+        var exp = matchElement.getAttribute("exp");
+        try {
+          blockEntry.matches[name] = new RegExp(exp, "m");
+          hasMatch = true;
+        } catch (e) {
+          // Ignore invalid regular expressions
+        }
+      }
+      if (matchElement.localName == "versionRange")
+        blockEntry.versions.push(new BlocklistItemData(matchElement));
+    }
+    // Plugin entries require *something* to match to an actual plugin
+    if (!hasMatch)
+      return;
+    // Add a default versionRange if there wasn't one specified
+    if (blockEntry.versions.length == 0)
+      blockEntry.versions.push(new BlocklistItemData(null));
+    result.push(blockEntry);
+  },
+
+  /* See nsIBlocklistService */
+  getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) {
+    if (!this._pluginEntries)
+      this._loadBlocklist();
+    return this._getPluginBlocklistState(plugin, this._pluginEntries,
+                                         appVersion, toolkitVersion);
+  },
+
+  /**
+   * Private version of getPluginBlocklistState that allows the caller to pass in
+   * the plugin blocklist entries.
+   *
+   * @param   plugin
+   *          The nsIPluginTag to get the blocklist state for.
+   * @param   pluginEntries
+   *          The plugin blocklist entries to compare against.
+   * @param   appVersion
+   *          The application version to compare to, will use the current
+   *          version if null.
+   * @param   toolkitVersion
+   *          The toolkit version to compare to, will use the current version if
+   *          null.
+   * @returns The blocklist state for the item, one of the STATE constants as
+   *          defined in nsIBlocklistService.
+   */
+  _getPluginBlocklistState: function(plugin, pluginEntries, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled)
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+    if (!appVersion)
+      appVersion = gApp.version;
+    if (!toolkitVersion)
+      toolkitVersion = gApp.platformVersion;
+
+    for each (var blockEntry in pluginEntries) {
+      var matchFailed = false;
+      for (var name in blockEntry.matches) {
+        if (!(name in plugin) ||
+            typeof(plugin[name]) != "string" ||
+            !blockEntry.matches[name].test(plugin[name])) {
+          matchFailed = true;
+          break;
+        }
+      }
+
+      if (matchFailed)
+        continue;
+
+      for (var i = 0; i < blockEntry.versions.length; i++) {
+        if (blockEntry.versions[i].includesItem(plugin.version, appVersion,
+                                                toolkitVersion)) {
+          if (blockEntry.versions[i].severity >= gBlocklistLevel)
+            return Ci.nsIBlocklistService.STATE_BLOCKED;
+          if (blockEntry.versions[i].severity == SEVERITY_OUTDATED)
+            return Ci.nsIBlocklistService.STATE_OUTDATED;
+          return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+        }
+      }
+    }
+
+    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+  },
+
+  _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
+    var addonList = [];
+
+    var em = Cc["@mozilla.org/extensions/manager;1"].
+             getService(Ci.nsIExtensionManager);
+    var addons = em.updateAndGetNewBlocklistedItems();
+
+    for (let i = 0; i < addons.length; i++) {
+      let oldState = -1;
+      if (oldAddonEntries)
+        oldState = this._getAddonBlocklistState(addons[i].id, addons[i].version,
+                                                oldAddonEntries);
+      let state = this.getAddonBlocklistState(addons[i].id, addons[i].version);
+      // We don't want to re-warn about items
+      if (state == oldState)
+        continue;
+
+      addonList.push({
+        name: addons[i].name,
+        version: addons[i].version,
+        icon: addons[i].iconURL,
+        disable: false,
+        blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+        item: addons[i]
+      });
+    }
+
+    var phs = Cc["@mozilla.org/plugin/host;1"].
+              getService(Ci.nsIPluginHost);
+    var plugins = phs.getPluginTags();
+
+    for (let i = 0; i < plugins.length; i++) {
+      let oldState = -1;
+      if (oldPluginEntries)
+        oldState = this._getPluginBlocklistState(plugins[i], oldPluginEntries);
+      let state = this.getPluginBlocklistState(plugins[i]);
+      // We don't want to re-warn about items
+      if (state == oldState)
+        continue;
+
+      if (plugins[i].blocklisted) {
+        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+          plugins[i].disabled = true;
+      }
+      else if (!plugins[i].disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+        if (state == Ci.nsIBlocklistService.STATE_OUTDATED) {
+          gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true);
+        }
+        else {
+          addonList.push({
+            name: plugins[i].name,
+            version: plugins[i].version,
+            icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
+            disable: false,
+            blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+            item: plugins[i]
+          });
+        }
+      }
+      plugins[i].blocklisted = state == Ci.nsIBlocklistService.STATE_BLOCKED;
+    }
+
+    if (addonList.length == 0)
+      return;
+
+    var args = {
+      restart: false,
+      list: addonList
+    };
+    // This lets the dialog get the raw js object
+    args.wrappedJSObject = args;
+
+    var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+             getService(Ci.nsIWindowWatcher);
+    ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
+                  "chrome,centerscreen,dialog,modal,titlebar", args);
+
+    for (let i = 0; i < addonList.length; i++) {
+      if (!addonList[i].disable)
+        continue;
+
+      if (addonList[i].item instanceof Ci.nsIUpdateItem)
+        em.disableItem(addonList[i].item.id);
+      else if (addonList[i].item instanceof Ci.nsIPluginTag)
+        addonList[i].item.disabled = true;
+      else
+        LOG("Blocklist::_blocklistUpdated: Unknown add-on type: " +
+            addonList[i].item);
+    }
+
+    if (args.restart)
+      restartApp();
+  },
+
+  classDescription: "Blocklist Service",
+  contractID: "@mozilla.org/extensions/blocklist;1",
+  classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsIBlocklistService,
+                                         Ci.nsITimerCallback]),
+  _xpcom_categories: [{ category: "update-timer",
+                        value: "@mozilla.org/extensions/blocklist;1," +
+                               "getService,blocklist-background-update-timer," +
+                               PREF_BLOCKLIST_INTERVAL + ",86400" }]
+};
+
+/**
+ * Helper for constructing a blocklist.
+ */
+function BlocklistItemData(versionRangeElement) {
+  var versionRange = this.getBlocklistVersionRange(versionRangeElement);
+  this.minVersion = versionRange.minVersion;
+  this.maxVersion = versionRange.maxVersion;
+  if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
+    this.severity = versionRangeElement.getAttribute("severity");
+  else
+    this.severity = DEFAULT_SEVERITY;
+  this.targetApps = { };
+  var found = false;
+
+  if (versionRangeElement) {
+    for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
+      var targetAppElement = versionRangeElement.childNodes.item(i);
+      if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
+          targetAppElement.localName != "targetApplication")
+        continue;
+      found = true;
+      // default to the current application if id is not provided.
+      var appID = targetAppElement.hasAttribute("id") ? targetAppElement.getAttribute("id") : gApp.ID;
+      this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
+    }
+  }
+  // Default to all versions of the current application when no targetApplication
+  // elements were found
+  if (!found)
+    this.targetApps[gApp.ID] = this.getBlocklistAppVersions(null);
+}
+
+BlocklistItemData.prototype = {
+  /**
+   * Tests if a version of an item is included in the version range and target
+   * application information represented by this BlocklistItemData using the
+   * provided application and toolkit versions.
+   * @param   version
+   *          The version of the item being tested.
+   * @param   appVersion
+   *          The application version to test with.
+   * @param   toolkitVersion
+   *          The toolkit version to test with.
+   * @returns True if the version range covers the item version and application
+   *          or toolkit version.
+   */
+  includesItem: function(version, appVersion, toolkitVersion) {
+    // Some platforms have no version for plugins, these don't match if there
+    // was a min/maxVersion provided
+    if (!version && (this.minVersion || this.maxVersion))
+      return false;
+
+    // Check if the item version matches
+    if (!this.matchesRange(version, this.minVersion, this.maxVersion))
+      return false;
+
+    // Check if the application version matches
+    if (this.matchesTargetRange(gApp.ID, appVersion))
+      return true;
+
+    // Check if the toolkit version matches
+    return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
+  },
+
+  /**
+   * Checks if a version is higher than or equal to the minVersion (if provided)
+   * and lower than or equal to the maxVersion (if provided).
+   * @param   version
+   *          The version to test.
+   * @param   minVersion
+   *          The minimum version. If null it is assumed that version is always
+   *          larger.
+   * @param   maxVersion
+   *          The maximum version. If null it is assumed that version is always
+   *          smaller.
+   */
+  matchesRange: function(version, minVersion, maxVersion) {
+    if (minVersion && gVersionChecker.compare(version, minVersion) < 0)
+      return false;
+    if (maxVersion && gVersionChecker.compare(version, maxVersion) > 0)
+      return false;
+    return true;
+  },
+
+  /**
+   * Tests if there is a matching range for the given target application id and
+   * version.
+   * @param   appID
+   *          The application ID to test for, may be for an application or toolkit
+   * @param   appVersion
+   *          The version of the application to test for.
+   * @returns True if this version range covers the application version given.
+   */
+  matchesTargetRange: function(appID, appVersion) {
+    var blTargetApp = this.targetApps[appID];
+    if (!blTargetApp)
+      return false;
+
+    for (var x = 0; x < blTargetApp.length; ++x) {
+      if (this.matchesRange(appVersion, blTargetApp[x].minVersion, blTargetApp[x].maxVersion))
+        return true;
+    }
+
+    return false;
+  },
+
+  /**
+   * Retrieves a version range (e.g. minVersion and maxVersion) for a
+   * blocklist item's targetApplication element.
+   * @param   targetAppElement
+   *          A targetApplication blocklist element.
+   * @returns An array of JS objects with the following properties:
+   *          "minVersion"  The minimum version in a version range (default = null).
+   *          "maxVersion"  The maximum version in a version range (default = null).
+   */
+  getBlocklistAppVersions: function(targetAppElement) {
+    var appVersions = [ ];
+
+    if (targetAppElement) {
+      for (var i = 0; i < targetAppElement.childNodes.length; ++i) {
+        var versionRangeElement = targetAppElement.childNodes.item(i);
+        if (!(versionRangeElement instanceof Ci.nsIDOMElement) ||
+            versionRangeElement.localName != "versionRange")
+          continue;
+        appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
+      }
+    }
+    // return minVersion = null and maxVersion = null if no specific versionRange
+    // elements were found
+    if (appVersions.length == 0)
+      appVersions.push(this.getBlocklistVersionRange(null));
+    return appVersions;
+  },
+
+  /**
+   * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
+   * versionRange element.
+   * @param   versionRangeElement
+   *          The versionRange blocklist element.
+   * @returns A JS object with the following properties:
+   *          "minVersion"  The minimum version in a version range (default = null).
+   *          "maxVersion"  The maximum version in a version range (default = null).
+   */
+  getBlocklistVersionRange: function(versionRangeElement) {
+    var minVersion = null;
+    var maxVersion = null;
+    if (!versionRangeElement)
+      return { minVersion: minVersion, maxVersion: maxVersion };
+
+    if (versionRangeElement.hasAttribute("minVersion"))
+      minVersion = versionRangeElement.getAttribute("minVersion");
+    if (versionRangeElement.hasAttribute("maxVersion"))
+      maxVersion = versionRangeElement.getAttribute("maxVersion");
+
+    return { minVersion: minVersion, maxVersion: maxVersion };
+  }
+};
+
+function NSGetModule(aCompMgr, aFileSpec) {
+  return XPCOMUtils.generateModule([Blocklist]);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/nsExtensionManager.js.in
@@ -0,0 +1,8361 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Extension Manager.
+#
+# The Initial Developer of the Original Code is Ben Goodger.
+# Portions created by the Initial Developer are Copyright (C) 2004
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#  Ben Goodger <ben@mozilla.org> (Google Inc.)
+#  Benjamin Smedberg <benjamin@smedbergs.us>
+#  Jens Bannmann <jens.b@web.de>
+#  Robert Strong <robert.bugzilla@gmail.com>
+#  Dave Townsend <dtownsend@oxymoronical.com>
+#  Daniel Veditz <dveditz@mozilla.com>
+#  Alexander J. Vincent <ajvincent@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+*/
+
+//
+// TODO:
+// - better logging
+//
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+const PREF_EM_CHECK_COMPATIBILITY     = "extensions.checkCompatibility";
+const PREF_EM_CHECK_UPDATE_SECURITY   = "extensions.checkUpdateSecurity";
+const PREF_EM_LAST_APP_VERSION        = "extensions.lastAppVersion";
+const PREF_EM_ENABLED_ITEMS           = "extensions.enabledItems";
+const PREF_UPDATE_COUNT               = "extensions.update.count";
+const PREF_UPDATE_DEFAULT_URL         = "extensions.update.url";
+const PREF_EM_NEW_ADDONS_LIST         = "extensions.newAddons";
+const PREF_EM_DISABLED_ADDONS_LIST    = "extensions.disabledAddons";
+const PREF_EM_SHOW_MISMATCH_UI        = "extensions.showMismatchUI";
+const PREF_EM_IGNOREMTIMECHANGES      = "extensions.ignoreMTimeChanges";
+const PREF_EM_DISABLEDOBSOLETE        = "extensions.disabledObsolete";
+const PREF_EM_EXTENSION_FORMAT        = "extensions.%UUID%.";
+const PREF_EM_ITEM_UPDATE_ENABLED     = "extensions.%UUID%.update.enabled";
+const PREF_EM_UPDATE_ENABLED          = "extensions.update.enabled";
+const PREF_EM_ITEM_UPDATE_URL         = "extensions.%UUID%.update.url";
+const PREF_EM_DSS_ENABLED             = "extensions.dss.enabled";
+const PREF_DSS_SWITCHPENDING          = "extensions.dss.switchPending";
+const PREF_DSS_SKIN_TO_SELECT         = "extensions.lastSelectedSkin";
+const PREF_LWTHEME_TO_SELECT          = "extensions.lwThemeToSelect";
+const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
+const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
+const PREF_EM_UPDATE_INTERVAL         = "extensions.update.interval";
+const PREF_UPDATE_NOTIFYUSER          = "extensions.update.notifyUser";
+const PREF_MATCH_OS_LOCALE            = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE            = "general.useragent.locale";
+
+const DIR_EXTENSIONS                  = "extensions";
+const DIR_CHROME                      = "chrome";
+const DIR_STAGE                       = "staged-xpis";
+const FILE_EXTENSIONS                 = "extensions.rdf";
+const FILE_EXTENSION_MANIFEST         = "extensions.ini";
+const FILE_EXTENSIONS_STARTUP_CACHE   = "extensions.cache";
+const FILE_EXTENSIONS_LOG             = "extensions.log";
+const FILE_INSTALL_MANIFEST           = "install.rdf";
+const FILE_CHROME_MANIFEST            = "chrome.manifest";
+
+const UNKNOWN_XPCOM_ABI               = "unknownABI";
+
+const TOOLKIT_ID                      = "toolkit@mozilla.org"
+
+const KEY_PROFILEDIR                  = "ProfD";
+const KEY_PROFILEDS                   = "ProfDS";
+const KEY_APPDIR                      = "XCurProcD";
+const KEY_TEMPDIR                     = "TmpD";
+
+const EM_ACTION_REQUESTED_TOPIC       = "em-action-requested";
+const EM_ITEM_INSTALLED               = "item-installed";
+const EM_ITEM_UPGRADED                = "item-upgraded";
+const EM_ITEM_UNINSTALLED             = "item-uninstalled";
+const EM_ITEM_ENABLED                 = "item-enabled";
+const EM_ITEM_DISABLED                = "item-disabled";
+const EM_ITEM_CANCEL                  = "item-cancel-action";
+
+const OP_NONE                         = "";
+const OP_NEEDS_INSTALL                = "needs-install";
+const OP_NEEDS_UPGRADE                = "needs-upgrade";
+const OP_NEEDS_UNINSTALL              = "needs-uninstall";
+const OP_NEEDS_ENABLE                 = "needs-enable";
+const OP_NEEDS_DISABLE                = "needs-disable";
+
+const KEY_APP_PROFILE                 = "app-profile";
+const KEY_APP_GLOBAL                  = "app-global";
+const KEY_APP_SYSTEM_LOCAL            = "app-system-local";
+const KEY_APP_SYSTEM_SHARE            = "app-system-share";
+const KEY_APP_SYSTEM_USER             = "app-system-user";
+
+const CATEGORY_INSTALL_LOCATIONS      = "extension-install-locations";
+const CATEGORY_UPDATE_PARAMS          = "extension-update-params";
+
+const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
+const PREFIX_ITEM_URI                 = "urn:mozilla:item:";
+const PREFIX_EXTENSION                = "urn:mozilla:extension:";
+const PREFIX_THEME                    = "urn:mozilla:theme:";
+const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
+const RDFURI_ITEM_ROOT                = "urn:mozilla:item:root"
+const RDFURI_DEFAULT_THEME            = "urn:mozilla:item:{972ce4c6-7e08-4474-a285-3208198ce6fd}";
+const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
+
+const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
+const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
+const URI_XPINSTALL_CONFIRM_DIALOG    = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
+const URI_EXTENSIONS_PROPERTIES       = "chrome://mozapps/locale/extensions/extensions.properties";
+const URI_BRAND_PROPERTIES            = "chrome://branding/locale/brand.properties";
+const URI_DOWNLOADS_PROPERTIES        = "chrome://mozapps/locale/downloads/downloads.properties";
+const URI_EXTENSION_UPDATE_DIALOG     = "chrome://mozapps/content/extensions/update.xul";
+const URI_EXTENSION_LIST_DIALOG       = "chrome://mozapps/content/extensions/list.xul";
+
+const URI_EXTENSION_MANAGER           = "chrome://mozapps/content/extensions/extensions.xul";
+const FEATURES_EXTENSION_MANAGER      = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable";
+const FEATURES_EXTENSION_UPDATES      = "chrome,centerscreen,extra-chrome,dialog,resizable,modal";
+
+/**
+ * Constants that internal code can use to indicate the reason for an add-on
+ * update check. external code uses other constants in nsIExtensionManager.idl.
+ */
+const MAX_PUBLIC_UPDATE_WHEN          = 15;
+const UPDATE_WHEN_PERIODIC_UPDATE     = 16;
+const UPDATE_WHEN_ADDON_INSTALLED     = 17;
+
+/**
+ * Bitmask of the different types of update check.
+ */
+const UPDATE_TYPE_COMPATIBILITY       = 32;
+const UPDATE_TYPE_NEWVERSION          = 64;
+
+const INSTALLERROR_SUCCESS               = 0;
+const INSTALLERROR_INVALID_VERSION       = -1;
+const INSTALLERROR_INVALID_GUID          = -2;
+const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
+const INSTALLERROR_PHONING_HOME          = -4;
+const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
+const INSTALLERROR_BLOCKLISTED           = -6;
+const INSTALLERROR_INSECURE_UPDATE       = -7;
+const INSTALLERROR_INVALID_MANIFEST      = -8;
+const INSTALLERROR_RESTRICTED            = -9;
+const INSTALLERROR_SOFTBLOCKED           = -10;
+
+const REQ_VERSION = 2;
+
+var gApp  = null;
+var gPref = null;
+var gRDF  = null;
+var gOS   = null;
+var gCheckCompatibilityPref;
+var gEmSingleton          = null;
+var gBlocklist            = null;
+var gXPCOMABI             = null;
+var gOSTarget             = null;
+var gConsole              = null;
+var gInstallManifestRoot  = null;
+var gVersionChecker       = null;
+var gLoggingEnabled       = null;
+var gCheckCompatibility   = true;
+var gCheckUpdateSecurity  = true;
+var gLocale               = "en-US";
+var gFirstRun             = false;
+var gAllowFlush           = true;
+var gDSNeedsFlush         = false;
+var gManifestNeedsFlush   = false;
+var gDefaultTheme         = "classic/1.0";
+
+/**
+ * Valid GUIDs fit this pattern.
+ */
+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;
+
+var gBranchVersion = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
+
+// shared code for suppressing bad cert dialogs
+XPCOMUtils.defineLazyGetter(this, "gCertUtils", function() {
+  let temp = { };
+  Components.utils.import("resource://gre/modules/CertUtils.jsm", temp);
+  return temp;
+});
+
+/**
+ * Creates a Version Checker object.
+ * @returns A handle to the global Version Checker service.
+ */
+function getVersionChecker() {
+  if (!gVersionChecker) {
+    gVersionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"].
+                      getService(Ci.nsIVersionComparator);
+  }
+  return gVersionChecker;
+}
+
+var BundleManager = {
+  /**
+  * Creates and returns a String Bundle at the specified URI
+  * @param   bundleURI
+  *          The URI of the bundle to load
+  * @returns A nsIStringBundle which was retrieved.
+  */
+  getBundle: function BundleManager_getBundle(bundleURI) {
+    var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
+              getService(Ci.nsIStringBundleService);
+    return sbs.createBundle(bundleURI);
+  },
+
+  _appName: "",
+
+  /**
+   * The Application's display name.
+   */
+  get appName() {
+    if (!this._appName) {
+      var brandBundle = this.getBundle(URI_BRAND_PROPERTIES)
+      this._appName = brandBundle.GetStringFromName("brandShortName");
+    }
+    return this._appName;
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Utility Functions
+//
+function EM_NS(property) {
+  return PREFIX_NS_EM + property;
+}
+
+function EM_R(property) {
+  return gRDF.GetResource(EM_NS(property));
+}
+
+function EM_L(literal) {
+  return gRDF.GetLiteral(literal);
+}
+
+function EM_I(integer) {
+  return gRDF.GetIntLiteral(integer);
+}
+
+function EM_D(integer) {
+  return gRDF.GetDateLiteral(integer);
+}
+
+/**
+ * Gets a preference value, handling the case where there is no default.
+ * @param   func
+ *          The name of the preference function to call, on nsIPrefBranch
+ * @param   preference
+ *          The name of the preference
+ * @param   defaultValue
+ *          The default value to return in the event the preference has
+ *          no setting
+ * @returns The value of the preference, or undefined if there was no
+ *          user or default value.
+ */
+function getPref(func, preference, defaultValue) {
+  try {
+    return gPref[func](preference);
+  }
+  catch (e) {
+  }
+  return defaultValue;
+}
+
+/**
+ * Initializes a RDF Container at a URI in a datasource.
+ * @param   datasource
+ *          The datasource the container is in
+ * @param   root
+ *          The RDF Resource which is the root of the container.
+ * @returns The nsIRDFContainer, initialized at the root.
+ */
+function getContainer(datasource, root) {
+  var ctr = Cc["@mozilla.org/rdf/container;1"].
+            createInstance(Ci.nsIRDFContainer);
+  ctr.Init(datasource, root);
+  return ctr;
+}
+
+/**
+ * Gets a RDF Resource for item with the given ID
+ * @param   id
+ *          The GUID of the item to construct a RDF resource to the
+ *          active item for
+ * @returns The RDF Resource to the Active item.
+ */
+function getResourceForID(id) {
+  return gRDF.GetResource(PREFIX_ITEM_URI + id);
+}
+
+/**
+ * Construct a nsIUpdateItem with the supplied metadata
+ * ...
+ */
+function makeItem(id, version, locationKey, minVersion, maxVersion, name,
+                  updateURL, updateHash, iconURL, updateRDF, updateKey, type, 
+                  targetAppID) {
+  var item = new UpdateItem();
+  item.init(id, version, locationKey, minVersion, maxVersion, name,
+            updateURL, updateHash, iconURL, updateRDF, updateKey, type,
+            targetAppID);
+  return item;
+}
+
+/**
+ * Gets the descriptor of a directory as a relative path to common base
+ * directories (profile, user home, app install dir, etc).
+ *
+ * @param   itemLocation
+ *          The nsILocalFile representing the item's directory.
+ * @param   installLocation the nsIInstallLocation for this item
+ */
+function getDescriptorFromFile(itemLocation, installLocation) {
+  var baseDir = installLocation.location;
+
+  if (baseDir && baseDir.contains(itemLocation, true)) {
+    return "rel%" + itemLocation.getRelativeDescriptor(baseDir);
+  }
+
+  return "abs%" + itemLocation.persistentDescriptor;
+}
+
+function getAbsoluteDescriptor(itemLocation) {
+  return itemLocation.persistentDescriptor;
+}
+
+/**
+ * Initializes a Local File object based on a descriptor
+ * provided by "getDescriptorFromFile".
+ *
+ * @param   descriptor
+ *          The descriptor that locates the directory
+ * @param   installLocation
+ *          The nsIInstallLocation object for this item.
+ * @returns The nsILocalFile object representing the location of the item
+ */
+function getFileFromDescriptor(descriptor, installLocation) {
+  var location = Cc["@mozilla.org/file/local;1"].
+                 createInstance(Ci.nsILocalFile);
+
+  var m = descriptor.match(/^(abs|rel)\%(.*)$/);
+  if (!m)
+    throw Cr.NS_ERROR_INVALID_ARG;
+
+  if (m[1] == "rel") {
+    location.setRelativeDescriptor(installLocation.location, m[2]);
+  }
+  else {
+    location.persistentDescriptor = m[2];
+  }
+
+  return location;
+}
+
+/**
+ * Determines if a file is an item package - either a XPI or a JAR file.
+ * @param   file
+ *          The file to check
+ * @returns true if the file is an item package, false otherwise.
+ */
+function fileIsItemPackage(file) {
+  var fileURL = getURIFromFile(file);
+  if (fileURL instanceof Ci.nsIURL)
+    var extension = fileURL.fileExtension.toLowerCase();
+  return extension == "xpi" || extension == "jar";
+}
+
+/**
+ * Deletes a directory and its children. First it tries nsIFile::Remove(true).
+ * If that fails it will fall back to recursing, setting the appropriate
+ * permissions, and deleting the current entry. This is needed for when we have
+ * rights to delete a directory but there are entries that have a read-only
+ * attribute (e.g. a copy restore from a read-only CD, etc.)
+ * @param   dir
+ *          A nsIFile for the directory to be deleted
+ */
+function removeDirRecursive(dir) {
+  try {
+    dir.remove(true);
+    return;
+  }
+  catch (e) {
+  }
+
+  var dirEntries = dir.directoryEntries;
+  while (dirEntries.hasMoreElements()) {
+    var entry = dirEntries.getNext().QueryInterface(Ci.nsIFile);
+
+    if (entry.isDirectory()) {
+      removeDirRecursive(entry);
+    }
+    else {
+      entry.permissions = FileUtils.PERMS_FILE;
+      entry.remove(false);
+    }
+  }
+  dir.permissions = FileUtils.PERMS_DIRECTORY;
+  dir.remove(true);
+}
+
+/**
+ * Logs a string to the error console and the text console if logging is
+ * enabled.
+ * @param   string
+ *          The log message.
+ */
+function LOG(string) {
+  if (gLoggingEnabled) {
+    dump("*** EM_LOG *** " + string + "\n");
+    if (gConsole)
+      gConsole.logStringMessage(string);
+  }
+}
+
+/**
+ * Logs a warning to the error console and if logging is enabled to the text
+ * console.
+ * @param   string
+ *          The warning message.
+ */
+function WARN(string) {
+  if (gLoggingEnabled)
+    dump("*** EM_WARN *** " + string + "\n");
+  if (gConsole) {
+    var message = Cc["@mozilla.org/scripterror;1"].
+                  createInstance(Ci.nsIScriptError);
+    message.init(string, null, null, 0, 0, Ci.nsIScriptError.warningFlag,
+                 "component javascript");
+    gConsole.logMessage(message);
+  }
+}
+
+/**
+ * Logs an error to the error console and to a permanent log file.
+ * @param   string
+ *          The error message.
+ */  
+function ERROR(string) {
+  if (gLoggingEnabled)
+    dump("*** EM_ERROR *** " + string + "\n");
+  if (gConsole) {
+    var message = Cc["@mozilla.org/scripterror;1"].
+                  createInstance(Ci.nsIScriptError);
+    message.init(string, null, null, 0, 0, Ci.nsIScriptError.errorFlag,
+                 "component javascript");
+    gConsole.logMessage(message);
+  }
+  try {
+    var tstamp = new Date();
+    var logfile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_EXTENSIONS_LOG]);
+    var stream = Cc["@mozilla.org/network/file-output-stream;1"].
+                 createInstance(Ci.nsIFileOutputStream);
+    stream.init(logfile, 0x02 | 0x08 | 0x10, 0666, 0); // write, create, append
+    var writer = Cc["@mozilla.org/intl/converter-output-stream;1"].
+                 createInstance(Ci.nsIConverterOutputStream);
+    writer.init(stream, "UTF-8", 0, 0x0000);
+    string = tstamp.toLocaleFormat("%Y-%m-%d %H:%M:%S - ") + string;
+    writer.writeString(string + "\n");
+    writer.close();
+  }
+  catch (e) { }
+}
+
+/**
+ * Randomize the specified file name. Used to force RDF to bypass the cache
+ * when loading certain types of files.
+ * @param   fileName
+ *          A file name to randomize, e.g. install.rdf
+ * @returns A randomized file name, e.g. install-xyz.rdf
+ */
+function getRandomFileName(fileName) {
+  var extensionDelimiter = fileName.lastIndexOf(".");
+  var prefix = fileName.substr(0, extensionDelimiter);
+  var suffix = fileName.substr(extensionDelimiter);
+
+  var characters = "abcdefghijklmnopqrstuvwxyz0123456789";
+  var nameString = prefix + "-";
+  for (var i = 0; i < 3; ++i) {
+    var index = Math.round((Math.random()) * characters.length);
+    nameString += characters.charAt(index);
+  }
+  return nameString + "." + suffix;
+}
+
+/**
+ * Get the RDF URI prefix of a nsIUpdateItem type. This function should be used
+ * ONLY to support Firefox 1.0 Update RDF files! Item URIs in the datasource
+ * are NOT prefixed.
+ * @param   type
+ *          The nsIUpdateItem type to find a RDF URI prefix for
+ * @returns The RDF URI prefix.
+ */
+function getItemPrefix(type) {
+  if (type & Ci.nsIUpdateItem.TYPE_EXTENSION)
+    return PREFIX_EXTENSION;
+  else if (type & Ci.nsIUpdateItem.TYPE_THEME)
+    return PREFIX_THEME;
+  return PREFIX_ITEM_URI;
+}
+
+/**
+ * Trims a prefix from a string.
+ * @param   string
+ *          The source string
+ * @param   prefix
+ *          The prefix to remove.
+ * @returns The suffix (string - prefix)
+ */
+function stripPrefix(string, prefix) {
+  return string.substr(prefix.length);
+}
+
+/**
+ * Gets a File URL spec for a nsIFile
+ * @param   file
+ *          The file to get a file URL spec to
+ * @returns The file URL spec to the file
+ */
+function getURLSpecFromFile(file) {
+  var ioServ = Cc["@mozilla.org/network/io-service;1"].
+               getService(Ci.nsIIOService);
+  var fph = ioServ.getProtocolHandler("file")
+                  .QueryInterface(Ci.nsIFileProtocolHandler);
+  return fph.getURLSpecFromFile(file);
+}
+
+/**
+ * Constructs a URI to a spec.
+ * @param   spec
+ *          The spec to construct a URI to
+ * @returns The nsIURI constructed.
+ */
+function newURI(spec) {
+  var ioServ = Cc["@mozilla.org/network/io-service;1"].
+               getService(Ci.nsIIOService);
+  return ioServ.newURI(spec, null, null);
+}
+
+/**
+ * Constructs a File URI to a nsIFile
+ * @param   file
+ *          The file to construct a File URI to
+ * @returns The file URI to the file
+ */
+function getURIFromFile(file) {
+  var ioServ = Cc["@mozilla.org/network/io-service;1"].
+               getService(Ci.nsIIOService);
+  return ioServ.newFileURI(file);
+}
+
+/**
+ * @returns Whether or not we are currently running in safe mode.
+ */
+function inSafeMode() {
+  return gApp.inSafeMode;
+}
+
+/**
+ * Extract the string value from a RDF Literal or Resource
+ * @param   literalOrResource
+ *          RDF String Literal or Resource
+ * @returns String value of the literal or resource, or undefined if the object
+ *          supplied is not a RDF string literal or resource.
+ */
+function stringData(literalOrResource) {
+  if (literalOrResource instanceof Ci.nsIRDFLiteral)
+    return literalOrResource.Value;
+  if (literalOrResource instanceof Ci.nsIRDFResource)
+    return literalOrResource.Value;
+  return undefined;
+}
+
+/**
+ * Extract the integer value of a RDF Literal
+ * @param   literal
+ *          nsIRDFInt literal
+ * @return  integer value of the literal
+ */
+function intData(literal) {
+  if (literal instanceof Ci.nsIRDFInt)
+    return literal.Value;
+  return undefined;
+}
+
+/**
+ * Gets a property from an install manifest.
+ * @param   installManifest
+ *          An Install Manifest datasource to read from
+ * @param   property
+ *          The name of a proprety to read (sans EM_NS)
+ * @returns The literal value of the property, or undefined if the property has
+ *          no value.
+ */
+function getManifestProperty(installManifest, property) {
+  var target = installManifest.GetTarget(gInstallManifestRoot,
+                                         gRDF.GetResource(EM_NS(property)), true);
+  var val = stringData(target);
+  return val === undefined ? intData(target) : val;
+}
+
+/**
+ * Given an Install Manifest Datasource, retrieves the type of item the manifest
+ * describes.
+ * @param   installManifest
+ *          The Install Manifest Datasource.
+ * @return  The nsIUpdateItem type of the item described by the manifest
+ *          returns TYPE_EXTENSION if attempts to determine the type fail.
+ */
+function getAddonTypeFromInstallManifest(installManifest) {
+  var target = installManifest.GetTarget(gInstallManifestRoot,
+                                         gRDF.GetResource(EM_NS("type")), true);
+  if (target) {
+    var type = stringData(target);
+    return type === undefined ? intData(target) : parseInt(type);
+  }
+
+  // Firefox 1.0 and earlier did not support addon-type annotation on the
+  // Install Manifest, so we fall back to a theme-only property to
+  // differentiate.
+  if (getManifestProperty(installManifest, "internalName") !== undefined)
+    return Ci.nsIUpdateItem.TYPE_THEME;
+
+  // If no type is provided, default to "Extension"
+  return Ci.nsIUpdateItem.TYPE_EXTENSION;
+}
+
+/**
+ * Shows a message about an incompatible Extension/Theme.
+ * @param   installData
+ *          An Install Data object from |getInstallData|
+ */
+function showIncompatibleError(installData) {
+  var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
+  var params = [extensionStrings.GetStringFromName("type-" + installData.type)];
+  var title = extensionStrings.formatStringFromName("incompatibleTitle",
+                                                    params, params.length);
+  params = [installData.name, installData.version, BundleManager.appName,
+            gApp.version];
+  var message = extensionStrings.formatStringFromName("incompatibleMessage",
+                                                      params, params.length);
+  var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+           getService(Ci.nsIPromptService);
+  ps.alert(null, title, message);
+}
+
+/**
+ * Shows a message.
+ * @param   titleKey
+ *          String key of the title string in the Extensions localization file.
+ * @param   messageKey
+ *          String key of the message string in the Extensions localization file.
+ * @param   messageParams
+ *          Array of strings to be substituted into |messageKey|. Can be null.
+ */
+function showMessage(titleKey, titleParams, messageKey, messageParams) {
+  var extensionStrings = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
+  if (titleParams && titleParams.length > 0) {
+    var title = extensionStrings.formatStringFromName(titleKey, titleParams,
+                                                      titleParams.length);
+  }
+  else
+    title = extensionStrings.GetStringFromName(titleKey);
+
+  if (messageParams && messageParams.length > 0) {
+    var message = extensionStrings.formatStringFromName(messageKey, messageParams,
+                                                        messageParams.length);
+  }
+  else
+    message = extensionStrings.GetStringFromName(messageKey);
+  var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+           getService(Ci.nsIPromptService);
+  ps.alert(null, title, message);
+}
+
+/**
+ * Shows a dialog for a blocklisted item. For soft blocked items this will
+ * return true if the item should still be installed
+ * @param   item
+ *          The nsIUpdateItem that is blocklisted
+ * @param   softblocked
+ *          True if this item is only soft blocked and may still be installed.
+ */
+function showBlocklistMessage(item, softblocked) {
+  var params = Cc["@mozilla.org/embedcomp/dialogparam;1"].
+               createInstance(Ci.nsIDialogParamBlock);
+  params.SetInt(0, softblocked ? 1 : 0);
+  params.SetInt(1, 0);
+  params.SetNumberStrings(1);
+  params.SetString(0, item.name + " " + item.version);
+
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+           getService(Ci.nsIWindowMediator);
+  var win = wm.getMostRecentWindow("Extension:Manager");
+  var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+           getService(Ci.nsIWindowWatcher);
+  ww.openWindow(win, URI_EXTENSION_LIST_DIALOG, "",
+                "chrome,centerscreen,modal,dialog,titlebar", params);
+
+  return params.GetInt(1) == 0 ? false : true;
+}
+
+/**
+ * Gets a zip reader for the file specified.
+ * @param   zipFile
+ *          A ZIP archive to open with a nsIZipReader.
+ * @return  A nsIZipReader for the file specified.
+ */
+function getZipReaderForFile(zipFile) {
+  try {
+    var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                    createInstance(Ci.nsIZipReader);
+    zipReader.open(zipFile);
+  }
+  catch (e) {
+    zipReader.close();
+    throw e;
+  }
+  return zipReader;
+}
+
+/**
+ * Verifies that a zip file's contents are all signed by the same principal.
+ * Directory entries and anything in the META-INF directory are not checked.
+ * @param   zip
+ *          A nsIZipReader to check
+ * @param   principal
+ *          The nsIPrincipal to compare against
+ * @return  true if all the contents were signed by the principal, false
+ *          otherwise.
+ */
+function verifyZipSigning(zip, principal) {
+  var count = 0;
+  var entries = zip.findEntries(null);
+  while (entries.hasMore()) {
+    var entry = entries.getNext();
+    // Nothing in META-INF is in the manifest.
+    if (entry.substr(0, 9) == "META-INF/")
+      continue;
+    // Directory entries aren't in the manifest.
+    if (entry.substr(-1) == "/")
+      continue;
+    count++;
+    var entryPrincipal = zip.getCertificatePrincipal(entry);
+    if (!entryPrincipal || !principal.equals(entryPrincipal))
+      return false;
+  }
+  return zip.manifestEntriesCount == count;
+}
+
+/**
+ * Extract a RDF file from a ZIP archive to a random location in the system
+ * temp directory.
+ * @param   zipFile
+ *          A ZIP archive to read from
+ * @param   fileName
+ *          The name of the file to read from the zip.
+ * @param   suppressErrors
+ *          Whether or not to report errors.
+ * @return  The file created in the temp directory.
+ */
+function extractRDFFileToTempDir(zipFile, fileName, suppressErrors) {
+  var file = FileUtils.getFile(KEY_TEMPDIR, [getRandomFileName(fileName)]);
+  try {
+    var zipReader = getZipReaderForFile(zipFile);
+    zipReader.extract(fileName, file);
+    zipReader.close();
+  }
+  catch (e) {
+    if (!suppressErrors) {
+      showMessage("missingFileTitle", [], "missingFileMessage",
+                  [BundleManager.appName, fileName]);
+      throw e;
+    }
+  }
+  return file;
+}
+
+/**
+ * Gets an Install Manifest datasource from a file.
+ * @param   file
+ *          The nsIFile that contains the Install Manifest RDF
+ * @returns The Install Manifest datasource
+ */
+function getInstallManifest(file) {
+  var uri = getURIFromFile(file);
+  try {
+    var fis = Cc["@mozilla.org/network/file-input-stream;1"].
+              createInstance(Ci.nsIFileInputStream);
+    fis.init(file, -1, -1, false);
+    var bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
+              createInstance(Ci.nsIBufferedInputStream);
+    bis.init(fis, 4096);
+    
+    var rdfParser = Cc["@mozilla.org/rdf/xml-parser;1"].
+                    createInstance(Ci.nsIRDFXMLParser)
+    var ds = Cc["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
+             createInstance(Ci.nsIRDFDataSource);
+    var listener = rdfParser.parseAsync(ds, uri);
+    var channel = Cc["@mozilla.org/network/input-stream-channel;1"].
+                  createInstance(Ci.nsIInputStreamChannel);
+    channel.setURI(uri);
+    channel.contentStream = bis;
+    channel.QueryInterface(Ci.nsIChannel);
+    channel.contentType = "text/xml";
+  
+    listener.onStartRequest(channel, null);
+    try {
+      var pos = 0;
+      var count = bis.available();
+      while (count > 0) {
+        listener.onDataAvailable(channel, null, bis, pos, count);
+        pos += count;
+        count = bis.available();
+      }
+      listener.onStopRequest(channel, null, Components.results.NS_OK);
+      bis.close();
+      fis.close();
+
+      var arcs = ds.ArcLabelsOut(gInstallManifestRoot);
+      if (arcs.hasMoreElements())
+        return ds;
+    }
+    catch (e) {
+      listener.onStopRequest(channel, null, e.result);
+      bis.close();
+      fis.close();
+    }
+  }
+  catch (e) { }
+
+  var url = uri.QueryInterface(Ci.nsIURL);
+  showMessage("malformedTitle", [], "malformedMessage",
+              [BundleManager.appName, url.fileName]);
+  return null;
+}
+
+/**
+ * Selects the closest matching localized resource in the given RDF resource
+ * @param   aDataSource The datasource to look in
+ * @param   aResource   The root resource containing the localized sections
+ * @returns The nsIRDFResource of the best em:localized section or null
+ *          if no valid match was found
+ */
+function findClosestLocalizedResource(aDataSource, aResource) {
+  var localizedProp = EM_R("localized");
+  var localeProp = EM_R("locale");
+
+  // Holds the best matching localized resource
+  var bestmatch = null;
+  // The number of locale parts it matched with
+  var bestmatchcount = 0;
+  // The number of locale parts in the match
+  var bestpartcount = 0;
+
+  var locales = [gLocale.toLowerCase()];
+  /* If the current locale is English then it will find a match if there is
+     a valid match for en-US so no point searching that locale too. */
+  if (locales[0].substring(0, 3) != "en-")
+    locales.push("en-us");
+
+  for each (var locale in locales) {
+    var lparts = locale.split("-");
+    var localizations = aDataSource.GetTargets(aResource, localizedProp, true);
+    while (localizations.hasMoreElements()) {
+      var localized = localizations.getNext().QueryInterface(Ci.nsIRDFNode);
+      var list = aDataSource.GetTargets(localized, localeProp, true);
+      while (list.hasMoreElements()) {
+        var found = stringData(list.getNext().QueryInterface(Ci.nsIRDFNode));
+        if (!found)
+          continue;
+
+        found = found.toLowerCase();
+
+        // Exact match is returned immediately
+        if (locale == found)
+          return localized;
+  
+        var fparts = found.split("-");
+        /* If we have found a possible match and this one isn't any longer
+           then we dont need to check further. */
+        if (bestmatch && fparts.length < bestmatchcount)
+          continue;
+  
+        // Count the number of parts that match
+        var maxmatchcount = Math.min(fparts.length, lparts.length);
+        var matchcount = 0;
+        while (matchcount < maxmatchcount &&
+               fparts[matchcount] == lparts[matchcount])
+          matchcount++;
+  
+        /* If we matched more than the last best match or matched the same and
+           this locale is less specific than the last best match. */
+        if (matchcount > bestmatchcount ||
+           (matchcount == bestmatchcount && fparts.length < bestpartcount)) {
+          bestmatch = localized;
+          bestmatchcount = matchcount;
+          bestpartcount = fparts.length;
+        }
+      }
+    }
+    // If we found a valid match for this locale return it
+    if (bestmatch)
+      return bestmatch;
+  }
+  return null;
+}
+    
+/**
+ * An enumeration of items in a JS array.
+ * @constructor
+ */
+function ArrayEnumerator(aItems) {
+  if (aItems) {
+    for (var i = 0; i < aItems.length; ++i) {
+      if (!aItems[i])
+        aItems.splice(i--, 1);
+    }
+    this._contents = aItems;
+  } else {
+    this._contents = [];
+  }
+}
+
+ArrayEnumerator.prototype = {
+  _index: 0,
+
+  hasMoreElements: function ArrayEnumerator_hasMoreElements() {
+    return this._index < this._contents.length;
+  },
+
+  getNext: function ArrayEnumerator_getNext() {
+    return this._contents[this._index++];
+  }
+};
+
+/**
+ * An enumeration of files in a JS array.
+ * @param   files
+ *          The files to enumerate
+ * @constructor
+ */
+function FileEnumerator(files) {
+  if (files) {
+    for (var i = 0; i < files.length; ++i) {
+      if (!files[i])
+        files.splice(i--, 1);
+    }
+    this._contents = files;
+  } else {
+    this._contents = [];
+  }
+}
+
+FileEnumerator.prototype = {
+  _index: 0,
+
+  /**
+   * Gets the next file in the sequence.
+   */
+  get nextFile() {
+    if (this._index < this._contents.length)
+      return this._contents[this._index++];
+    return null;
+  },
+
+  /**
+   * Stop enumerating. Nothing to do here.
+   */
+  close: function FileEnumerator_close() {
+  }
+};
+
+/**
+ * An object which identifies an Install Location for items, where the location
+ * relationship is each item living in a directory named with its GUID under
+ * the directory used when constructing this object.
+ *
+ * e.g. <location>\{GUID1}
+ *      <location>\{GUID2}
+ *      <location>\{GUID3}
+ *      ...
+ *
+ * @param   name
+ *          The string identifier of this Install Location.
+ * @param   location
+ *          The directory that contains the items.
+ * @constructor
+ */
+function DirectoryInstallLocation(name, location, restricted, priority, independent) {
+  this._name = name;
+  if (location.exists()) {
+    if (!location.isDirectory())
+      throw new Error("location must be a directoy!");
+  }
+  else {
+    try {
+      location.create(Ci.nsILocalFile.DIRECTORY_TYPE, 0775);
+    }
+    catch (e) {
+      LOG("DirectoryInstallLocation: failed to create location " +
+          " directory = " + location.path + ", exception = " + e + "\n");
+    }
+  }
+
+  this._location = location;
+  this._locationToIDMap = {};
+  this._restricted = restricted;
+  this._priority = priority;
+  this._independent = independent;
+}
+DirectoryInstallLocation.prototype = {
+  _name           : "",
+  _location       : null,
+  _locationToIDMap: null,
+  _restricted     : false,
+  _priority       : 0,
+  _independent    : false,
+  _canAccess      : null,
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  get name() {
+    return this._name;
+  },
+
+  /**
+   * Reads a directory linked to in a file.
+   * @param   file
+   *          The file containing the directory path
+   * @returns A nsILocalFile object representing the linked directory.
+   */
+  _readDirectoryFromFile: function DirInstallLocation__readDirectoryFromFile(file) {
+    var fis = Cc["@mozilla.org/network/file-input-stream;1"].
+              createInstance(Ci.nsIFileInputStream);
+    fis.init(file, -1, -1, false);
+    var line = { value: "" };
+    if (fis instanceof Ci.nsILineInputStream)
+      fis.readLine(line);
+    fis.close();
+    if (line.value) {
+      var linkedDirectory = Cc["@mozilla.org/file/local;1"].
+                            createInstance(Ci.nsILocalFile);
+      try {
+        linkedDirectory.initWithPath(line.value);
+      }
+      catch (e) {
+        linkedDirectory.setRelativeDescriptor(file.parent, line.value);
+      }
+
+      return linkedDirectory;
+    }
+    return null;
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  get itemLocations() {
+    var locations = [];
+    if (!this._location.exists())
+      return new FileEnumerator(locations);
+
+    try {
+      var entries = this._location.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+      while (true) {
+        var entry = entries.nextFile;
+        if (!entry)
+          break;
+        entry instanceof Ci.nsILocalFile;
+        if (!entry.isDirectory() && gIDTest.test(entry.leafName)) {
+          var linkedDirectory = this._readDirectoryFromFile(entry);
+          if (linkedDirectory && linkedDirectory.exists() &&
+              linkedDirectory.isDirectory()) {
+            locations.push(linkedDirectory);
+            this._locationToIDMap[linkedDirectory.persistentDescriptor] = entry.leafName;
+          }
+        }
+        else
+          locations.push(entry);
+      }
+      entries.close();
+    }
+    catch (e) {
+    }
+    return new FileEnumerator(locations);
+  },
+
+  /**
+   * Retrieves the GUID for an item at the specified location.
+   * @param   file
+   *          The location where an item might live.
+   * @returns The ID for an item that might live at the location specified.
+   *
+   * N.B. This function makes no promises about whether or not this path is
+   *      actually maintained by this Install Location.
+   */
+  getIDForLocation: function DirInstallLocation_getIDForLocation(file) {
+    var section = file.leafName;
+    var filePD = file.persistentDescriptor;
+    if (filePD in this._locationToIDMap)
+      section = this._locationToIDMap[filePD];
+
+    if (gIDTest.test(section))
+      return RegExp.$1;
+    return undefined;
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  get location() {
+    return this._location.clone();
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  get restricted() {
+    return this._restricted;
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  get canAccess() {
+    if (this._canAccess != null)
+      return this._canAccess;
+
+    if (!this.location.exists()) {
+      this._canAccess = false;
+      return false;
+    }
+
+    var testFile = this.location;
+    testFile.append("Access Privileges Test");
+    try {
+      testFile.createUnique(Ci.nsILocalFile.DIRECTORY_TYPE,
+                            FileUtils.PERMS_DIRECTORY);
+      testFile.remove(false);
+      this._canAccess = true;
+    }
+    catch (e) {
+      this._canAccess = false;
+    }
+    return this._canAccess;
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  get priority() {
+    return this._priority;
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  getItemLocation: function DirInstallLocation_getItemLocation(id) {
+    var itemLocation = this.location;
+    itemLocation.append(id);
+    if (itemLocation.exists() && !itemLocation.isDirectory())
+      return this._readDirectoryFromFile(itemLocation);
+    if (!itemLocation.exists() && this.canAccess)
+      itemLocation.create(Ci.nsILocalFile.DIRECTORY_TYPE,
+                          FileUtils.PERMS_DIRECTORY);
+    return itemLocation;
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  itemIsManagedIndependently: function DirInstallLocation_itemIsManagedIndependently(id) {
+    if (this._independent)
+      return true;
+    var itemLocation = this.location;
+    itemLocation.append(id);
+    return itemLocation.exists() && !itemLocation.isDirectory();
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  getItemFile: function DirInstallLocation_getItemFile(id, filePath) {
+    var itemLocation = this.getItemLocation(id);
+    var parts = filePath.split("/");
+    for (var i = 0; i < parts.length; ++i)
+      itemLocation.append(parts[i]);
+    return itemLocation;
+  },
+
+  /**
+   * Stages the specified file for later.
+   * @param   file
+   *          The file to stage
+   * @param   id
+   *          The GUID of the item the file represents
+   */
+  stageFile: function DirInstallLocation_stageFile(file, id) {
+    var stagedFile = this.location;
+    stagedFile.append(DIR_STAGE);
+    stagedFile.append(id);
+    stagedFile.append(file.leafName);
+
+    // When an incompatible update is successful the file is already staged
+    if (stagedFile.equals(file))
+      return stagedFile;
+
+    if (stagedFile.exists())
+      stagedFile.remove(false);
+
+    file.copyTo(stagedFile.parent, stagedFile.leafName);
+
+    // If the file has incorrect permissions set, correct them now.
+    if (!stagedFile.isWritable())
+      stagedFile.permissions = FileUtils.PERMS_FILE;
+
+    return stagedFile;
+  },
+
+  /**
+   * Returns the most recently staged package (e.g. the last XPI or JAR in a
+   * directory) for an item and removes items that do not qualify.
+   * @param   id
+   *          The ID of the staged package
+   * @returns an nsIFile if the package exists otherwise null.
+   */
+  getStageFile: function DirInstallLocation_getStageFile(id) {
+    var stageFile = null;
+    var stageDir = this.location;
+    stageDir.append(DIR_STAGE);
+    stageDir.append(id);
+    if (!stageDir.exists() || !stageDir.isDirectory())
+      return null;
+    try {
+      var entries = stageDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+      while (entries.hasMoreElements()) {
+        var file = entries.nextFile;
+        if (!(file instanceof Ci.nsILocalFile))
+          continue;
+        if (file.isDirectory())
+          removeDirRecursive(file);
+        else if (fileIsItemPackage(file)) {
+          if (stageFile)
+            stageFile.remove(false);
+          stageFile = file;
+        }
+        else
+          file.remove(false);
+      }
+    }
+    catch (e) {
+    }
+    if (entries instanceof Ci.nsIDirectoryEnumerator)
+      entries.close();
+    return stageFile;
+  },
+
+  /**
+   * Removes a file from the stage. This cleans up the stage if there is nothing
+   * else left after the remove operation.
+   * @param   file
+   *          The file to remove.
+   */
+  removeFile: function DirInstallLocation_removeFile(file) {
+    if (file.exists())
+      file.remove(false);
+    var parent = file.parent;
+    var entries = parent.directoryEntries;
+    try {
+      // XXXrstrong calling hasMoreElements on a nsIDirectoryEnumerator after
+      // it has been removed will cause a crash on Mac OS X - bug 292823
+      while (parent && !parent.equals(this.location) &&
+            !entries.hasMoreElements()) {
+        parent.remove(false);
+        parent = parent.parent;
+        entries = parent.directoryEntries;
+      }
+      if (entries instanceof Ci.nsIDirectoryEnumerator)
+        entries.close();
+    }
+    catch (e) {
+      ERROR("DirectoryInstallLocation::removeFile: failed to remove staged " +
+            " directory = " + parent.path + ", exception = " + e + "\n");
+    }
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInstallLocation])
+};
+
+#ifdef XP_WIN
+
+const nsIWindowsRegKey = Ci.nsIWindowsRegKey;
+
+/**
+ * An object that identifies the location of installed items based on entries
+ * in the Windows registry.  For each application a subkey is defined that
+ * contains a set of values, where the name of each value is a GUID and the
+ * contents of the value is a filesystem path identifying a directory
+ * containing an installed item.
+ *
+ * @param   name
+ *          The string identifier of this Install Location.
+ * @param   rootKey
+ *          The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
+ * @param   restricted
+ *          Indicates that the location may be restricted (e.g., this is
+ *          usually true of a system level install location).
+ * @param   priority
+ *          The priority of this install location.
+ * @constructor
+ */
+function WinRegInstallLocation(name, rootKey, restricted, priority) {
+  this._name = name;
+  this._rootKey = rootKey;
+  this._restricted = restricted;
+  this._priority = priority;
+  this._IDToDirMap = {};
+  this._DirToIDMap = {};
+
+  // Reading the registry may throw an exception, and that's ok.  In error
+  // cases, we just leave ourselves in the empty state.
+  try {
+    var path = this._appKeyPath + "\\Extensions";
+    var key = Cc["@mozilla.org/windows-registry-key;1"].
+              createInstance(nsIWindowsRegKey);
+    key.open(this._rootKey, path, nsIWindowsRegKey.ACCESS_READ);
+    this._readAddons(key);
+  } catch (e) {
+    if (key)
+      key.close();
+  }
+}
+WinRegInstallLocation.prototype = {
+  _name       : "",
+  _rootKey    : null,
+  _restricted : false,
+  _priority   : 0,
+  _IDToDirMap : null,  // mapping from ID to directory object
+  _DirToIDMap : null,  // mapping from directory path to ID
+
+  /**
+   * Retrieves the path of this Application's data key in the registry.
+   */
+  get _appKeyPath() {
+    var appVendor = gApp.vendor;
+    var appName = gApp.name;
+
+#ifdef MOZ_THUNDERBIRD
+    // XXX Thunderbird doesn't specify a vendor string!!
+    if (appVendor == "")
+      appVendor = "Mozilla";
+#endif
+
+    // XULRunner-based apps may intentionally not specify a vendor:
+    if (appVendor != "")
+      appVendor += "\\";
+
+    return "SOFTWARE\\" + appVendor + appName;
+  },
+
+  /**
+   * Read the registry and build a mapping between GUID and directory for each
+   * installed item.
+   * @param   key
+   *          The key that contains the GUID->path pairs
+   */
+  _readAddons: function RegInstallLocation__readAddons(key) {
+    var count = key.valueCount;
+    for (var i = 0; i < count; ++i) {
+      var id = key.getValueName(i);
+
+      var dir = Cc["@mozilla.org/file/local;1"].
+                createInstance(Ci.nsILocalFile);
+      dir.initWithPath(key.readStringValue(id));
+
+      if (dir.exists() && dir.isDirectory()) {
+        this._IDToDirMap[id] = dir;
+        this._DirToIDMap[dir.path] = id;
+      }
+    }
+  },
+
+  get name() {
+    return this._name;
+  },
+
+  get itemLocations() {
+    var locations = [];
+    for (var id in this._IDToDirMap) {
+      locations.push(this._IDToDirMap[id]);
+    }
+    return new FileEnumerator(locations);
+  },
+
+  get location() {
+    return null;
+  },
+
+  get restricted() {
+    return this._restricted;
+  },
+
+  // you should never be able to write to this location
+  get canAccess() {
+    return false;
+  },
+
+  get priority() {
+    return this._priority;
+  },
+
+  getItemLocation: function RegInstallLocation_getItemLocation(id) {
+    if (!(id in this._IDToDirMap))
+      return null;
+    return this._IDToDirMap[id].clone();
+  },
+
+  getIDForLocation: function RegInstallLocation_getIDForLocation(dir) {
+    return this._DirToIDMap[dir.path];
+  },
+
+  getItemFile: function RegInstallLocation_getItemFile(id, filePath) {
+    var itemLocation = this.getItemLocation(id);
+    if (!itemLocation)
+      return null;
+    var parts = filePath.split("/");
+    for (var i = 0; i < parts.length; ++i)
+      itemLocation.append(parts[i]);
+    return itemLocation;
+  },
+
+  itemIsManagedIndependently: function RegInstallLocation_itemIsManagedIndependently(id) {
+    return true;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIInstallLocation])
+};
+
+#endif
+
+/**
+ * Safely attempt to install or uninstall a given item ID in an install
+ * location. Using aggressive success-safety checks, this function will attempt
+ * to move an existing location for an item aside and then allow installation
+ * into the appropriate folder. If any operation fails the installation will
+ * abort and roll back from the moved-aside old version.
+ * @param   itemID
+ *          The GUID of the item to perform the operation on.
+ * @param   installLocation
+ *          The Install Location where the item is installed.
+ * @param   file
+ *          An xpi file to install to the location or null to just uninstall
+ */
+function safeInstallOperation(itemID, installLocation, file) {
+  var movedFiles = [];
+
+  /**
+   * Reverts a deep move by moving backed up files back to their original
+   * location.
+   */
+  function rollbackMove()
+  {
+    for (var i = 0; i < movedFiles.length; ++i) {
+      var oldFile = movedFiles[i].oldFile;
+      var newFile = movedFiles[i].newFile;
+      try {
+        newFile.moveTo(oldFile.parent, newFile.leafName);
+      }
+      catch (e) {
+        ERROR("safeInstallOperation: failed to roll back files after an install " +
+              "operation failed. Failed to roll back: " + newFile.path + " to: " +
+              oldFile.path + " ... aborting installation.");
+        throw e;
+      }
+    }
+  }
+
+  /**
+   * Moves a file to a new folder.
+   * @param   file
+   *          The file to move
+   * @param   destination
+   *          The target folder
+   */
+  function moveFile(file, destination) {
+    try {
+      var oldFile = file.clone();
+      file.moveTo(destination, file.leafName);
+      movedFiles.push({ oldFile: oldFile, newFile: file });
+    }
+    catch (e) {
+      ERROR("safeInstallOperation: failed to back up file: " + file.path + " to: " +
+            destination.path + " ... rolling back file moves and aborting " +
+            "installation.");
+      rollbackMove();
+      throw e;
+    }
+  }
+
+  /**
+   * Moves a directory to a new location. If any part of the move fails,
+   * files already moved will be rolled back.
+   * @param   sourceDir
+   *          The directory to move
+   * @param   targetDir
+   *          The destination directory
+   * @param   currentDir
+   *          The current directory (a subdirectory of |sourceDir| or
+   *          |sourceDir| itself) we are moving files from.
+   */
+  function moveDirectory(sourceDir, targetDir, currentDir) {
+    var entries = currentDir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+    while (true) {
+      var entry = entries.nextFile;
+      if (!entry)
+        break;
+      if (entry.isDirectory())
+        moveDirectory(sourceDir, targetDir, entry);
+      else if (entry instanceof Ci.nsILocalFile) {
+        var rd = entry.getRelativeDescriptor(sourceDir);
+        var destination = targetDir.clone().QueryInterface(Ci.nsILocalFile);
+        destination.setRelativeDescriptor(targetDir, rd);
+        moveFile(entry, destination.parent);
+      }
+    }
+    entries.close();
+  }
+
+  /**
+   * Removes the temporary backup directory where we stored files.
+   * @param   directory
+   *          The backup directory to remove
+   */
+  function cleanUpTrash(directory) {
+    try {
+      // Us-generated. Safe.
+      if (directory && directory.exists())
+        removeDirRecursive(directory);
+    }
+    catch (e) {
+      ERROR("safeInstallOperation: failed to clean up the temporary backup of the " +
+            "older version: " + itemLocationTrash.path);
+      // This is a non-fatal error. Annoying, but non-fatal.
+    }
+  }
+
+  if (!installLocation.itemIsManagedIndependently(itemID)) {
+    var itemLocation = installLocation.getItemLocation(itemID);
+    if (itemLocation.exists()) {
+      var trashDirName = itemID + "-trash";
+      var itemLocationTrash = itemLocation.parent.clone();
+      itemLocationTrash.append(trashDirName);
+      if (itemLocationTrash.exists()) {
+        // We can remove recursively here since this is a folder we created, not
+        // one the user specified. If this fails, it'll throw, and the caller
+        // should stop installation.
+        try {
+          removeDirRecursive(itemLocationTrash);
+        }
+        catch (e) {
+          ERROR("safeFileOperation: failed to remove existing trash directory " +
+                itemLocationTrash.path + " ... aborting installation.");
+          throw e;
+        }
+      }
+
+      // Move the directory that contains the existing version of the item aside,
+      // into {GUID}-trash. This will throw if there's a failure and the install
+      // will abort.
+      moveDirectory(itemLocation, itemLocationTrash, itemLocation);
+
+      // Clean up the original location, if necessary. Again, this is a path we
+      // generated, so it is safe to recursively delete.
+      try {
+        removeDirRecursive(itemLocation);
+      }
+      catch (e) {
+        ERROR("safeInstallOperation: failed to clean up item location after its contents " +
+              "were properly backed up. Failed to clean up: " + itemLocation.path +
+              " ... rolling back file moves and aborting installation.");
+        rollbackMove();
+        cleanUpTrash(itemLocationTrash);
+        throw e;
+      }
+    }
+  }
+  else if (installLocation.name == KEY_APP_PROFILE ||
+           installLocation.name == KEY_APP_GLOBAL ||
+           installLocation.name == KEY_APP_SYSTEM_USER) {
+    // Check for a pointer file and move it aside if it exists
+    var pointerFile = installLocation.location.clone();
+    pointerFile.append(itemID);
+    if (pointerFile.exists() && !pointerFile.isDirectory()) {
+      var trashFileName = itemID + "-trash";
+      var itemLocationTrash = installLocation.location.clone();
+      itemLocationTrash.append(trashFileName);
+      if (itemLocationTrash.exists()) {
+        // We can remove recursively here since this is a folder we created, not
+        // one the user specified. If this fails, it'll throw, and the caller
+        // should stop installation.
+        try {
+          removeDirRecursive(itemLocationTrash);
+        }
+        catch (e) {
+          ERROR("safeFileOperation: failed to remove existing trash directory " +
+                itemLocationTrash.path + " ... aborting installation.");
+          throw e;
+        }
+      }
+      itemLocationTrash.create(Ci.nsILocalFile.DIRECTORY_TYPE,
+                               FileUtils.PERMS_DIRECTORY);
+      // Move the pointer file to the trash.
+      moveFile(pointerFile, itemLocationTrash);
+    }
+  }
+
+  if (file) {
+    // Extract the xpi's files into the new directory
+    try {
+      var zipReader = getZipReaderForFile(file);
+
+      // create directories first
+      var entries = zipReader.findEntries("*/");
+      while (entries.hasMore()) {
+        var entryName = entries.getNext();
+        var target = installLocation.getItemFile(itemID, entryName);
+        if (!target.exists()) {
+          try {
+            target.create(Ci.nsILocalFile.DIRECTORY_TYPE,
+                          FileUtils.PERMS_DIRECTORY);
+          }
+          catch (e) {
+            ERROR("extractFiles: failed to create target directory for extraction " +
+                  "file = " + target.path + ", exception = " + e + "\n");
+          }
+        }
+      }
+
+      entries = zipReader.findEntries(null);
+      while (entries.hasMore()) {
+        var entryName = entries.getNext();
+        target = installLocation.getItemFile(itemID, entryName);
+        if (target.exists())
+          continue;
+
+        zipReader.extract(entryName, target);
+        target.permissions = FileUtils.PERMS_FILE;
+      }
+    }
+    catch (e) {
+      // This means the install operation failed. Remove everything and roll back.
+      ERROR("safeInstallOperation: file extraction failed, " +
+            "rolling back file moves and aborting installation.");
+      try {
+        // Us-generated. Safe.
+        removeDirRecursive(itemLocation);
+      }
+      catch (e) {
+        ERROR("safeInstallOperation: failed to remove the folder we failed to install " +
+              "an item into: " + itemLocation.path + " -- There is not much to suggest " +
+              "here... maybe restart and try again?");
+        cleanUpTrash(itemLocationTrash);
+        throw e;
+      }
+      rollbackMove();
+      cleanUpTrash(itemLocationTrash);
+      throw e;
+    }
+    finally {
+      if (zipReader)
+        zipReader.close();
+    }
+  }
+
+  // Now, and only now - after everything else has succeeded (against all odds!)
+  // remove the {GUID}-trash directory where we stashed the old version of the
+  // item.
+  cleanUpTrash(itemLocationTrash);
+}
+
+/**
+ * Manages the list of pending operations.
+ */
+var PendingOperations = {
+  _ops: { },
+
+  /**
+   * Adds an entry to the Pending Operations List
+   * @param   opType
+   *          The type of Operation to be performed
+   * @param   entry
+   *          A JS Object representing the item to be operated on:
+   *          "locationKey"   The name of the Install Location where the item
+   *                          is installed.
+   *          "id"            The GUID of the item.
+   */
+  addItem: function PendingOperations_addItem(opType, entry) {
+    if (opType == OP_NONE)
+      this.clearOpsForItem(entry.id);
+    else {
+      if (!(opType in this._ops))
+        this._ops[opType] = { };
+      this._ops[opType][entry.id] = entry.locationKey;
+    }
+  },
+
+  /**
+   * Removes a Pending Operation from the list
+   * @param   opType
+   *          The type of Operation being removed
+   * @param   id
+   *          The GUID of the item to remove the entry for
+   */
+  clearItem: function PendingOperations_clearItem(opType, id) {
+    if (opType in this._ops && id in this._ops[opType])
+      delete this._ops[opType][id];
+  },
+
+  /**
+   * Removes all Pending Operation for an item
+   * @param   id
+   *          The ID of the item to remove the entries for
+   */
+  clearOpsForItem: function PendingOperations_clearOpsForItem(id) {
+    for (var opType in this._ops) {
+      if (id in this._ops[opType])
+        delete this._ops[opType][id];
+    }
+  },
+
+  /**
+   * Remove all Pending Operations of a certain type
+   * @param   opType
+   *          The type of Operation to remove all entries for
+   */
+  clearItems: function PendingOperations_clearItems(opType) {
+    if (opType in this._ops)
+      delete this._ops[opType];
+  },
+
+  /**
+   * Get an array of operations of a certain type
+   * @param   opType
+   *          The type of Operation to return a list of
+   */
+  getOperations: function PendingOperations_getOperations(opType) {
+    if (!(opType in this._ops))
+      return [];
+    var ops = [];
+    for (var id in this._ops[opType])
+      ops.push( {id: id, locationKey: this._ops[opType][id] } );
+    return ops;
+  },
+
+  /**
+   * The total number of Pending Operations, for all types.
+   */
+  get size() {
+    var size = 0;
+    for (var opType in this._ops) {
+      for (var id in this._ops[opType])
+        ++size;
+    }
+    return size;
+  }
+};
+
+/**
+ * Manages registered Install Locations
+ */
+var InstallLocations = {
+  _locations: { },
+
+  /**
+   * A nsISimpleEnumerator of all available Install Locations.
+   */
+  get enumeration() {
+    var installLocations = [];
+    for (var key in this._locations)
+      installLocations.push(InstallLocations.get(key));
+    return new ArrayEnumerator(installLocations);
+  },
+
+  /**
+   * Gets a named Install Location
+   * @param   name
+   *          The name of the Install Location to get
+   */
+  get: function InstallLocations_get(name) {
+    return name in this._locations ? this._locations[name] : null;
+  },
+
+  /**
+   * Registers an Install Location
+   * @param   installLocation
+   *          The Install Location to register
+   */
+  put: function InstallLocations_put(installLocation) {
+    this._locations[installLocation.name] = installLocation;
+  }
+};
+
+/**
+ * Manages the Startup Cache. The Startup Cache is a representation
+ * of the contents of extensions.cache, a list of all
+ * items the Extension System knows about, whether or not they
+ * are active or visible.
+ */
+var StartupCache = {
+  /**
+   * Location Name -> GUID hash of entries from the Startup Cache file
+   * Each entry has the following properties:
+   *  "descriptor"    The location on disk of the item
+   *  "mtime"         The time the location was last modified
+   *  "op"            Any pending operations on this item.
+   *  "location"      The Install Location name where the item is installed.
+   */
+  entries: { },
+
+  /**
+   * Puts an entry into the Startup Cache
+   * @param   installLocation
+   *          The Install Location where the item is installed
+   * @param   id
+   *          The GUID of the item
+   * @param   op
+   *          The name of the operation to be performed
+   * @param   shouldCreate
+   *          Whether or not we should create a new entry for this item
+   *          in the cache if one does not already exist.
+   */
+  put: function StartupCache_put(installLocation, id, op, shouldCreate) {
+    var itemLocation = installLocation.getItemLocation(id);
+
+    var descriptor = null;
+    var mtime = null;
+    if (itemLocation) {
+      itemLocation.QueryInterface(Ci.nsILocalFile);
+      descriptor = getDescriptorFromFile(itemLocation, installLocation);
+      if (itemLocation.exists() && itemLocation.isDirectory())
+        mtime = Math.floor(itemLocation.lastModifiedTime / 1000);
+    }
+
+    this._putRaw(installLocation.name, id, descriptor, mtime, op, shouldCreate);
+  },
+
+  /**
+   * Private helper function for putting an entry into the Startup Cache
+   * without relying on the presence of its associated nsIInstallLocation
+   * instance.
+   *
+   * @param key
+   *        The install location name.
+   * @param id
+   *        The ID of the item.
+   * @param descriptor
+   *        Value returned from absoluteDescriptor.  May be null, in which
+   *        case the descriptor field is not updated.
+   * @param mtime
+   *        The last modified time of the item.  May be null, in which case the
+   *        descriptor field is not updated.
+   * @param op
+   *        The OP code to store with the entry.
+   * @param shouldCreate
+   *        Boolean value indicating whether to create or delete the entry.
+   */
+  _putRaw: function StartupCache__putRaw(key, id, descriptor, mtime, op, shouldCreate) {
+    if (!(key in this.entries))
+      this.entries[key] = { };
+    if (!(id in this.entries[key]))
+      this.entries[key][id] = { };
+    if (shouldCreate) {
+      if (!this.entries[key][id])
+        this.entries[key][id] = { };
+
+      var entry = this.entries[key][id];
+
+      if (descriptor)
+        entry.descriptor = descriptor;
+      if (mtime)
+        entry.mtime = mtime;
+      entry.op = op;
+      entry.location = key;
+    }
+    else
+      this.entries[key][id] = null;
+  },
+
+  /**
+   * Clears an entry from the Startup Cache
+   * @param   installLocation
+   *          The Install Location where item is installed
+   * @param   id
+   *          The GUID of the item.
+   */
+  clearEntry: function StartupCache_clearEntry(installLocation, id) {
+    var key = installLocation.name;
+    if (key in this.entries && id in this.entries[key])
+      this.entries[key][id] = null;
+  },
+
+  /**
+   * Get all the startup cache entries for a particular ID.
+   * @param   id
+   *          The GUID of the item to locate.
+   * @returns An array of Startup Cache entries for the specified ID.
+   */
+  findEntries: function StartupCache_findEntries(id) {
+    var entries = [];
+    for (var key in this.entries) {
+      if (id in this.entries[key])
+        entries.push(this.entries[key][id]);
+    }
+    return entries;
+  },
+
+  /**
+   * Read the Item-Change manifest file into a hash of properties.
+   * The Item-Change manifest currently holds a list of paths, with the last
+   * mtime for each path, and the GUID of the item at that path.
+   */
+  read: function StartupCache_read() {
+    var itemChangeManifest = FileUtils.getFile(KEY_PROFILEDIR,
+                                               [FILE_EXTENSIONS_STARTUP_CACHE]);
+    if (!itemChangeManifest.exists()) {
+      // There is no change manifest for some reason, either we're in an initial
+      // state or something went wrong with one of the other files and the
+      // change manifest was removed. Return an empty dataset and rebuild.
+      gFirstRun = true;
+      return;
+    }
+    var fis = Cc["@mozilla.org/network/file-input-stream;1"].
+              createInstance(Ci.nsIFileInputStream);
+    fis.init(itemChangeManifest, -1, -1, false);
+    if (fis instanceof Ci.nsILineInputStream) {
+      var line = { value: "" };
+      var more = false;
+      do {
+        more = fis.readLine(line);
+        if (line.value) {
+          // The Item-Change manifest is formatted like so:
+          //  (pd = descriptor)
+          // location-key\tguid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
+          // location-key\tguid-of-item\tpd-to-extension2\tmtime-of-pd\tpending-op
+          // ...
+          // We hash on location-key first, because we don't want to have to
+          // spin up the main extensions datasource on every start to determine
+          // the Install Location for an item.
+          // We hash on guid second, because we want a way to quickly determine
+          // item GUID during a check loop that runs on every startup.
+          var parts = line.value.split("\t");
+          // Silently drop any entries in unknown install locations
+          if (!InstallLocations.get(parts[0]))
+            continue;
+          var op = parts[4];
+          this._putRaw(parts[0], parts[1], parts[2], parts[3], op, true);
+          if (op)
+            PendingOperations.addItem(op, { locationKey: parts[0], id: parts[1] });
+        }
+      }
+      while (more);
+    }
+    fis.close();
+  },
+
+  /**
+   * Writes the Startup Cache to disk
+   */
+  write: function StartupCache_write() {
+    var extensionsCacheFile = FileUtils.getFile(KEY_PROFILEDIR,
+                                                [FILE_EXTENSIONS_STARTUP_CACHE]);
+    var fos = FileUtils.openSafeFileOutputStream(extensionsCacheFile);
+    for (var locationKey in this.entries) {
+      for (var id in this.entries[locationKey]) {
+        var entry = this.entries[locationKey][id];
+        if (entry) {
+          try {
+            var itemLocation = getFileFromDescriptor(entry.descriptor, InstallLocations.get(locationKey));
+
+            // Update our knowledge of this item's last-modified-time.
+            // XXXdarin: this may cause us to miss changes in some cases.
+            var itemMTime = 0;
+            if (itemLocation.exists() && itemLocation.isDirectory())
+              itemMTime = Math.floor(itemLocation.lastModifiedTime / 1000);
+
+            // Each line in the startup cache manifest is in this form:
+            // location-key\tid-of-item\tpd-to-extension1\tmtime-of-pd\tpending-op
+            var line = locationKey + "\t" + id + "\t" + entry.descriptor + "\t" +
+                       itemMTime + "\t" + entry.op + "\r\n";
+            fos.write(line, line.length);
+          }
+          catch (e) {}
+        }
+      }
+    }
+    FileUtils.closeSafeFileOutputStream(fos);
+  }
+};
+
+/**
+ * Installs, manages and tracks compatibility for Extensions and Themes
+ * @constructor
+ */
+function ExtensionManager() {
+  gApp = Cc["@mozilla.org/xre/app-info;1"].
+         getService(Ci.nsIXULAppInfo).QueryInterface(Ci.nsIXULRuntime);
+  gOSTarget = gApp.OS;
+  try {
+    gXPCOMABI = gApp.XPCOMABI;
+  } catch (ex) {
+    // Provide a default for gXPCOMABI. It won't be compared to an
+    // item's metadata (i.e. install.rdf can't specify e.g. WINNT_unknownABI
+    // as targetPlatform), but it will be displayed in error messages and
+    // transmitted to update URLs.
+    gXPCOMABI = UNKNOWN_XPCOM_ABI;
+  }
+  gPref = Cc["@mozilla.org/preferences-service;1"].
+          getService(Ci.nsIPrefBranch2).
+          QueryInterface(Ci.nsIPrefService);
+  var defaults = gPref.getDefaultBranch("");
+  try {
+    gDefaultTheme = defaults.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
+  } catch(e) {}
+
+  gOS = Cc["@mozilla.org/observer-service;1"].
+        getService(Ci.nsIObserverService);
+  gOS.addObserver(this, "xpcom-shutdown", false);
+  gOS.addObserver(this, "lightweight-theme-preview-requested", false);
+  gOS.addObserver(this, "lightweight-theme-change-requested", false);
+
+  gConsole = Cc["@mozilla.org/consoleservice;1"].
+             getService(Ci.nsIConsoleService);
+
+  gRDF = Cc["@mozilla.org/rdf/rdf-service;1"].
+         getService(Ci.nsIRDFService);
+  gInstallManifestRoot = gRDF.GetResource(RDFURI_INSTALL_MANIFEST_ROOT);
+
+  // Register Global Install Location
+  var appGlobalExtensions = FileUtils.getDir(KEY_APPDIR, [DIR_EXTENSIONS],
+                                             false);
+  var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL;
+  var globalLocation = new DirectoryInstallLocation(KEY_APP_GLOBAL,
+                                                    appGlobalExtensions, true,
+                                                    priority, false);
+  InstallLocations.put(globalLocation);
+
+  // Register App-Profile Install Location
+  var appProfileExtensions = FileUtils.getDir(KEY_PROFILEDS, [DIR_EXTENSIONS],
+                                              false);
+  var priority = Ci.nsIInstallLocation.PRIORITY_APP_PROFILE;
+  var profileLocation = new DirectoryInstallLocation(KEY_APP_PROFILE,
+                                                     appProfileExtensions, false,
+                                                     priority, false);
+  InstallLocations.put(profileLocation);
+
+  // Register per-user Install Location
+  try {
+    var appSystemUExtensions = FileUtils.getDir("XREUSysExt", [gApp.ID], false);
+  }
+  catch(e) { }
+
+  if (appSystemUExtensions) {
+    var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_USER;
+    var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_USER,
+                                                      appSystemUExtensions, false,
+                                                      priority, true);
+
+    InstallLocations.put(systemLocation);
+  }
+
+  // Register App-System-Shared Install Location
+  try {
+    var appSystemSExtensions = FileUtils.getDir("XRESysSExtPD", [gApp.ID], false);
+  }
+  catch (e) { }
+
+  if (appSystemSExtensions) {
+    var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10;
+    var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_SHARE,
+                                                      appSystemSExtensions, true,
+                                                      priority, true);
+    InstallLocations.put(systemLocation);
+  }
+
+  // Register App-System-Local Install Location
+  try {
+    var appSystemLExtensions = FileUtils.getDir("XRESysLExtPD", [gApp.ID], false);
+  }
+  catch (e) { }
+
+  if (appSystemLExtensions) {
+    var priority = Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 20;
+    var systemLocation = new DirectoryInstallLocation(KEY_APP_SYSTEM_LOCAL,
+                                                      appSystemLExtensions, true,
+                                                      priority, true);
+    InstallLocations.put(systemLocation);
+  }
+
+#ifdef XP_WIN
+  // Register HKEY_LOCAL_MACHINE Install Location
+  InstallLocations.put(
+      new WinRegInstallLocation("winreg-app-global",
+                                nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+                                true,
+                                Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_GLOBAL + 10));
+
+  // Register HKEY_CURRENT_USER Install Location
+  InstallLocations.put(
+      new WinRegInstallLocation("winreg-app-user",
+                                nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                                false,
+                                Ci.nsIInstallLocation.PRIORITY_APP_SYSTEM_USER + 10));
+#endif
+
+  // Register Additional Install Locations
+  var categoryManager = Cc["@mozilla.org/categorymanager;1"].
+                        getService(Ci.nsICategoryManager);
+  var locations = categoryManager.enumerateCategory(CATEGORY_INSTALL_LOCATIONS);
+  while (locations.hasMoreElements()) {
+    var entry = locations.getNext().QueryInterface(Ci.nsISupportsCString).data;
+    var contractID = categoryManager.getCategoryEntry(CATEGORY_INSTALL_LOCATIONS, entry);
+    var location = Cc[contractID].getService(Ci.nsIInstallLocation);
+    InstallLocations.put(location);
+  }
+}
+
+ExtensionManager.prototype = {
+  /**
+   * See nsIObserver.idl
+   */
+  observe: function EM_observe(subject, topic, data) {
+    switch (topic) {
+    case "profile-after-change":
+      this._profileSelected();
+      break;
+    case "quit-application-requested":
+      this._confirmCancelDownloadsOnQuit(subject);
+      break;
+    case "offline-requested":
+      this._confirmCancelDownloadsOnOffline(subject);
+      break;
+    case "lightweight-theme-preview-requested":
+      if (gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
+        let cancel = subject.QueryInterface(Ci.nsISupportsPRBool);
+        cancel.data = true;
+      }
+      break;
+    case "lightweight-theme-change-requested":
+      let theme = JSON.parse(data);
+      if (!theme)
+        return;
+
+      if (gPref.prefHasUserValue(PREF_GENERAL_SKINS_SELECTEDSKIN)) {
+        if (getPref("getBoolPref", PREF_EM_DSS_ENABLED, false)) {
+          gPref.clearUserPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
+          return;
+        }
+
+        let cancel = subject.QueryInterface(Ci.nsISupportsPRBool);
+        cancel.data = true;
+        gPref.setBoolPref(PREF_DSS_SWITCHPENDING, true);
+        gPref.setCharPref(PREF_DSS_SKIN_TO_SELECT, gDefaultTheme);
+        gPref.setCharPref(PREF_LWTHEME_TO_SELECT, theme.id);
+
+        // Open the UI so the user can see that a restart is necessary
+        var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+                 getService(Ci.nsIWindowMediator);
+        var win = wm.getMostRecentWindow("Extension:Manager");
+
+        if (win) {
+          win.showView("themes");
+          return;
+        }
+
+        var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+                 getService(Ci.nsIWindowWatcher);
+        var param = Cc["@mozilla.org/supports-array;1"].
+                    createInstance(Ci.nsISupportsArray);
+        var arg = Cc["@mozilla.org/supports-string;1"].
+                  createInstance(Ci.nsISupportsString);
+        arg.data = "themes";
+        param.AppendElement(arg);
+        ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_MANAGER, param);
+        return;
+      }
+      else {
+        // Cancel any pending theme change and allow the lightweight theme
+        // change to go ahead
+        if (gPref.prefHasUserValue(PREF_DSS_SWITCHPENDING))
+          gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
+        if (gPref.prefHasUserValue(PREF_DSS_SKIN_TO_SELECT))
+          gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+      }
+      break;
+    case "xpcom-shutdown":
+      this._shutdown();
+      break;
+    case "nsPref:changed":
+      if (data == PREF_EM_LOGGING_ENABLED)
+        this._loggingToggled();
+      else if (data == gCheckCompatibilityPref ||
+               data == PREF_EM_CHECK_UPDATE_SECURITY)
+        this._updateAppDisabledState();
+      else if ((data == PREF_MATCH_OS_LOCALE) || (data == PREF_SELECTED_LOCALE))
+        this._updateLocale();
+      break;
+    }
+  },
+
+  /**
+   * Refresh the logging enabled global from preferences when the user changes
+   * the preference settting.
+   */
+  _loggingToggled: function EM__loggingToggled() {
+    gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
+  },
+
+  /**
+   * Retrieves the current locale
+   */
+  _updateLocale: function EM__updateLocale() {
+    try {
+      if (gPref.getBoolPref(PREF_MATCH_OS_LOCALE)) {
+        var localeSvc = Cc["@mozilla.org/intl/nslocaleservice;1"].
+                        getService(Ci.nsILocaleService);
+        gLocale = localeSvc.getLocaleComponentForUserAgent();
+        return;
+      }
+    }
+    catch (ex) {
+    }
+    gLocale = gPref.getCharPref(PREF_SELECTED_LOCALE);
+  },
+
+  /**
+   * When a preference is toggled that affects whether an item is usable or not
+   * we must app-enable or app-disable the item based on the new settings.
+   */
+  _updateAppDisabledState: function EM__updateAppDisabledState() {
+    gCheckCompatibility = getPref("getBoolPref", gCheckCompatibilityPref, true);
+    gCheckUpdateSecurity = getPref("getBoolPref", PREF_EM_CHECK_UPDATE_SECURITY, true);
+    var ds = this.datasource;
+
+    // Enumerate all items
+    var ctr = getContainer(ds, ds._itemRoot);
+    var elements = ctr.GetElements();
+    while (elements.hasMoreElements()) {
+      var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
+
+      // App disable or enable items as necessary
+      // _appEnableItem and _appDisableItem will do nothing if the item is already
+      // in the right state.
+      var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
+      if (this._isUsableItem(id))
+        this._appEnableItem(id);
+      else
+        this._appDisableItem(id);
+    }
+  },
+
+  /**
+   * Initialize the system after a profile has been selected.
+   */
+  _profileSelected: function EM__profileSelected() {
+    // Tell the Chrome Registry which Skin to select
+    try {
+      if (gPref.getBoolPref(PREF_DSS_SWITCHPENDING)) {
+        var toSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
+        gPref.setCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN, toSelect);
+        gPref.clearUserPref(PREF_DSS_SWITCHPENDING);
+        gPref.clearUserPref(PREF_DSS_SKIN_TO_SELECT);
+
+        // If we've changed to a non-default theme make sure there is no
+        // lightweight theme selected
+        if (toSelect != gDefaultTheme) {
+          if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT))
+            gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
+          LightweightThemeManager.currentTheme = null;
+        }
+      }
+
+      if (gPref.prefHasUserValue(PREF_LWTHEME_TO_SELECT)) {
+        var id = gPref.getCharPref(PREF_LWTHEME_TO_SELECT);
+        if (id)
+          LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme(id);
+        else
+          LightweightThemeManager.currentTheme = null;
+        gPref.clearUserPref(PREF_LWTHEME_TO_SELECT);
+      }
+    }
+    catch (e) {
+    }
+
+    var version = gApp.version.replace(gBranchVersion, "$1");
+    gCheckCompatibilityPref = PREF_EM_CHECK_COMPATIBILITY + "." + version;
+
+    gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
+    gCheckCompatibility = getPref("getBoolPref", gCheckCompatibilityPref, true);
+    gCheckUpdateSecurity = getPref("getBoolPref", PREF_EM_CHECK_UPDATE_SECURITY, true);
+
+    if ("nsICrashReporter" in Ci && gApp instanceof Ci.nsICrashReporter) {
+      // Annotate the crash report with relevant add-on information.
+      try {
+        gApp.annotateCrashReport("Add-ons", gPref.getCharPref(PREF_EM_ENABLED_ITEMS));
+      } catch (e) { }
+      try {
+        gApp.annotateCrashReport("Theme", gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN));
+      } catch (e) { }
+      try {
+        gApp.annotateCrashReport("EMCheckCompatibility", gCheckCompatibility);
+      } catch (e) { }
+    }
+
+    gPref.addObserver("extensions.", this, false);
+    gPref.addObserver(PREF_MATCH_OS_LOCALE, this, false);
+    gPref.addObserver(PREF_SELECTED_LOCALE, this, false);
+    this._updateLocale();
+  },
+
+  /**
+   * Notify user that there are new addons updates
+   */
+  _showUpdatesWindow: function EM__showUpdatesWindow() {
+    if (!getPref("getBoolPref", PREF_UPDATE_NOTIFYUSER, false))
+      return;
+
+    var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+             getService(Ci.nsIWindowWatcher);
+    var param = Cc["@mozilla.org/supports-array;1"].
+                createInstance(Ci.nsISupportsArray);
+    var arg = Cc["@mozilla.org/supports-string;1"].
+              createInstance(Ci.nsISupportsString);
+    arg.data = "updates-only";
+    param.AppendElement(arg);
+    ww.openWindow(null, URI_EXTENSION_MANAGER, null, FEATURES_EXTENSION_UPDATES, param);
+  },
+
+  /**
+   * Clean up on application shutdown to avoid leaks.
+   */
+  _shutdown: function EM__shutdown() {
+    if (!gAllowFlush) {
+      // Something went wrong and there are potentially flushes pending.
+      ERROR("Reached _shutdown and without clearing any pending flushes");
+      try {
+        gAllowFlush = true;
+        if (gManifestNeedsFlush) {
+          gManifestNeedsFlush = false;
+          this._updateManifests(false);
+        }
+        if (gDSNeedsFlush) {
+          gDSNeedsFlush = false;
+          this.datasource.Flush();
+        }
+      }
+      catch (e) {
+        ERROR("Error flushing caches: " + e);
+      }
+    }
+
+    gOS.removeObserver(this, "xpcom-shutdown");
+    gOS.removeObserver(this, "lightweight-theme-preview-requested");
+    gOS.removeObserver(this, "lightweight-theme-change-requested");
+
+    // Release strongly held services.
+    gOS = null;
+    if (this._ds) {
+      gRDF.UnregisterDataSource(this._ptr);
+      this._ptr = null;
+      this._ds.shutdown();
+      this._ds = null;
+    }
+    gRDF = null;
+    if (gPref) {
+      gPref.removeObserver("extensions.", this);
+      gPref.removeObserver(PREF_MATCH_OS_LOCALE, this);
+      gPref.removeObserver(PREF_SELECTED_LOCALE, this);
+    }
+    gPref = null;
+    gConsole = null;
+    gVersionChecker = null;
+    gInstallManifestRoot = null;
+    gApp = null;
+  },
+
+  /**
+   * Check for presence of critical Extension system files. If any is missing,
+   * delete the others and signal that the system needs to rebuild them all
+   * from scratch.
+   * @returns true if any critical file is missing and the system needs to
+   *          be rebuilt, false otherwise.
+   */
+  _ensureDatasetIntegrity: function EM__ensureDatasetIntegrity() {
+    var profD = FileUtils.getDir(KEY_PROFILEDIR, [], false);
+    var extensionsDS = profD.clone();
+    extensionsDS.append(FILE_EXTENSIONS);
+    var extensionsINI = profD.clone();
+    extensionsINI.append(FILE_EXTENSION_MANIFEST);
+    var extensionsCache = profD;
+    extensionsCache.append(FILE_EXTENSIONS_STARTUP_CACHE);
+
+    var dsExists = extensionsDS.exists();
+    var iniExists = extensionsINI.exists();
+    var cacheExists = extensionsCache.exists();
+
+    if (dsExists && iniExists && cacheExists)
+      return [false, !iniExists];
+
+    // If any of the files are missing, remove the .ini file
+    if (iniExists)
+      extensionsINI.remove(false);
+
+    // If the extensions datasource is missing remove the .cache file if it exists
+    if (!dsExists && cacheExists)
+      extensionsCache.remove(false);
+
+    return [true, !iniExists];
+  },
+
+  /**
+   * See nsIExtensionManager.idl
+   */
+  start: function EM_start() {
+    var isDirty, forceAutoReg;
+
+    // Check for missing manifests - e.g. missing extensions.ini, missing
+    // extensions.cache, extensions.rdf etc. If any of these files
+    // is missing then we are in some kind of weird or initial state and need
+    // to force a regeneration.
+    [isDirty, forceAutoReg] = this._ensureDatasetIntegrity();
+
+    // Block attempts to flush for the entire startup
+    gAllowFlush = false;
+
+    // Configure any items that are being installed, uninstalled or upgraded
+    // by being added, removed or modified by another process. We must do this
+    // on every startup since there is no way we can tell if this has happened
+    // or not!
+    if (this._checkForFileChanges())
+      isDirty = true;
+
+    this._showUpdatesWindow();
+
+    if (PendingOperations.size != 0)
+      isDirty = true;
+
+    var needsRestart = false;
+    // Extension Changes
+    if (isDirty) {
+      needsRestart = this._finishOperations();
+
+      if (forceAutoReg) {
+        this._extensionListChanged = true;
+        needsRestart = true;
+      }
+    }
+
+    // Resume flushing and perform a flush for anything that was deferred
+    try {
+      gAllowFlush = true;
+      if (gManifestNeedsFlush) {
+        gManifestNeedsFlush = false;
+        this._updateManifests(false);
+      }
+      if (gDSNeedsFlush) {
+        gDSNeedsFlush = false;
+        this.datasource.Flush();
+      }
+    }
+    catch (e) {
+      ERROR("Error flushing caches: " + e);
+    }
+
+    return needsRestart;
+  },
+
+  /**
+   * Notified when a timer fires
+   * @param   timer
+   *          The timer that fired
+   */
+  notify: function EM_notify(timer) {
+    if (!getPref("getBoolPref", PREF_EM_UPDATE_ENABLED, true))
+      return;
+
+    var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY);
+
+    var updater = new ExtensionItemUpdater(this);
+    updater.checkForUpdates(items, items.length,
+                            Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION,
+                            new BackgroundUpdateCheckListener(this.datasource),
+                            UPDATE_WHEN_PERIODIC_UPDATE);
+
+    LightweightThemeManager.updateCurrentTheme();
+  },
+
+  /**
+   * Check to see if a file is a XPI/JAR file that the user dropped into this
+   * Install Location. (i.e. a XPI that is not a staged XPI from an install
+   * transaction that is currently in operation).
+   * @param   file
+   *          The XPI/JAR file to configure
+   * @param   location
+   *          The Install Location where this file was found.
+   * @returns A nsIUpdateItem representing the dropped XPI if this file was a
+   *          XPI/JAR that needs installation, null otherwise.
+   */
+  _getItemForDroppedFile: function EM__getItemForDroppedFile(file, location) {
+    if (fileIsItemPackage(file)) {
+      // We know nothing about this item, it is not something we've
+      // staged in preparation for finalization, so assume it's something
+      // the user dropped in.
+      LOG("A Item Package appeared at: " + file.path + " that we know " +
+          "nothing about, assuming it was dropped in by the user and " +
+          "configuring for installation now. Location Key: " + location.name);
+
+      var installManifestFile = extractRDFFileToTempDir(file, FILE_INSTALL_MANIFEST, true);
+      if (!installManifestFile.exists())
+        return null;
+      var installManifest = getInstallManifest(installManifestFile);
+      installManifestFile.remove(false);
+      var ds = this.datasource;
+      var installData = this._getInstallData(installManifest);
+      var targetAppInfo = ds.getTargetApplicationInfo(installData.id, installManifest);
+      return makeItem(installData.id,
+                      installData.version,
+                      location.name,
+                      targetAppInfo ? targetAppInfo.minVersion : "",
+                      targetAppInfo ? targetAppInfo.maxVersion : "",
+                      getManifestProperty(installManifest, "name"),
+                      "", /* XPI Update URL */
+                      "", /* XPI Update Hash */
+                      getManifestProperty(installManifest, "iconURL"),
+                      getManifestProperty(installManifest, "updateURL"),
+                      getManifestProperty(installManifest, "updateKey"),
+                      installData.type,
+                      targetAppInfo ? targetAppInfo.appID : gApp.ID);
+    }
+    return null;
+  },
+
+  /**
+   * Configure an item that was installed or upgraded by another process
+   * so that |_finishOperations| can properly complete processing and
+   * registration.
+   * As this is the only point at which we can reliably know the Install
+   * Location of this item, we use this as an opportunity to:
+   * 1. Check that this item is compatible with this Firefox version.
+   * 2. If it is, configure the item by using the supplied callback.
+   *    We do not do any special handling in the case that the item is
+   *    not compatible with this version other than to simply not register
+   *    it and log that fact - there is no "phone home" check for updates.
+   *    It may or may not make sense to do this, but for now we'll just
+   *    not register.
+   * @param   id
+   *          The GUID of the item to validate and configure.
+   * @param   location
+   *          The Install Location where this item is installed.
+   * @param   callback
+   *          The callback that configures the item for installation upon
+   *          successful validation.
+   */
+   installItem: function EM_installItem(id, location, callback) {
+    // As this is the only pint at which we reliably know the installation
+    var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
+    if (installRDF.exists()) {
+      LOG("Item Installed/Upgraded at Install Location: " + location.name +
+          " Item ID: " + id + ", attempting to register...");
+      var installManifest = getInstallManifest(installRDF);
+      var installData = this._getInstallData(installManifest);
+      if (installData.error == INSTALLERROR_SUCCESS) {
+        LOG("... success, item is compatible");
+        callback(installManifest, installData.id, location, installData.type);
+      }
+      else if (installData.error == INSTALLERROR_INCOMPATIBLE_VERSION) {
+        LOG("... success, item installed but is not compatible");
+        callback(installManifest, installData.id, location, installData.type);
+        this._appDisableItem(id);
+      }
+      else if (installData.error == INSTALLERROR_INSECURE_UPDATE) {
+        LOG("... success, item installed but does not provide updates securely");
+        callback(installManifest, installData.id, location, installData.type);
+        this._appDisableItem(id);
+      }
+      else if (installData.error == INSTALLERROR_BLOCKLISTED) {
+        LOG("... success, item installed but is blocklisted");
+        callback(installManifest, installData.id, location, installData.type);
+        this._appDisableItem(id);
+      }
+      else if (installData.error == INSTALLERROR_SOFTBLOCKED) {
+        LOG("... success, item installed but is soft blocked, item will be disabled");
+        callback(installManifest, installData.id, location, installData.type);
+        this.disableItem(id);
+      }
+      else {
+        /**
+         * Turns an error code into a message for logging
+         * @param   error
+         *          an Install Error code
+         * @returns A string message to be logged.
+         */
+        function translateErrorMessage(error) {
+          switch (error) {
+          case INSTALLERROR_INVALID_GUID:
+            return "Invalid GUID";
+          case INSTALLERROR_INVALID_VERSION:
+            return "Invalid Version";
+          case INSTALLERROR_INCOMPATIBLE_PLATFORM:
+            return "Incompatible Platform";
+          }
+        }
+        LOG("... failure, item is not compatible, error: " +
+            translateErrorMessage(installData.error));
+
+        // Add the item to the Startup Cache anyway, so we don't re-detect it
+        // every time the app starts.
+        StartupCache.put(location, id, OP_NONE, true);
+      }
+    }
+  },
+
+  /**
+   * Check for changes to items that were made independently of the Extension
+   * Manager, e.g. items were added or removed from a Install Location or items
+   * in an Install Location changed.
+   */
+  _checkForFileChanges: function EM__checkForFileChanges() {
+    var em = this;
+
+    /**
+     * Determines if an item can be used based on whether or not the install
+     * location of the "item" has an equal or higher priority than the install
+     * location where another version may live.
+     * @param   id
+     *          The GUID of the item being installed.
+     * @param   location
+     *          The location where an item is to be installed.
+     * @returns true if the item can be installed at that location, false
+     *          otherwise.
+     */
+    function canUse(id, location) {
+      for (var locationKey in StartupCache.entries) {
+        if (locationKey != location.name &&
+            id in StartupCache.entries[locationKey]) {
+          if (StartupCache.entries[locationKey][id]) {
+            var oldInstallLocation = InstallLocations.get(locationKey);
+            if (oldInstallLocation.priority <= location.priority)
+              return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+      * Gets a Dialog Param Block loaded with a set of strings to initialize the
+      * XPInstall Confirmation Dialog.
+      * @param   strings
+      *          An array of strings
+      * @returns A nsIDialogParamBlock loaded with the strings and dialog state.
+      */
+    function getParamBlock(strings) {
+      var dpb = Cc["@mozilla.org/embedcomp/dialogparam;1"].
+                createInstance(Ci.nsIDialogParamBlock);
+      // OK and Cancel Buttons
+      dpb.SetInt(0, 2);
+      // Number of Strings
+      dpb.SetInt(1, strings.length);
+      dpb.SetNumberStrings(strings.length);
+      // Add Strings
+      for (var i = 0; i < strings.length; ++i)
+        dpb.SetString(i, strings[i]);
+
+      var supportsString = Cc["@mozilla.org/supports-string;1"].
+                           createInstance(Ci.nsISupportsString);
+      var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
+      supportsString.data = bundle.GetStringFromName("droppedInWarning");
+      var objs = Cc["@mozilla.org/array;1"].
+                 createInstance(Ci.nsIMutableArray);
+      objs.appendElement(supportsString, false);
+      dpb.objects = objs;
+      return dpb;
+    }
+
+    /**
+     * Installs a set of files which were dropped into an install location by
+     * the user, only after user confirmation.
+     * @param   droppedInFiles
+     *          An array of JS objects with the following properties:
+     *          "file"      The nsILocalFile where the XPI lives
+     *          "location"  The Install Location where the XPI was found.
+     * @param   xpinstallStrings
+     *          An array of strings used to initialize the XPInstall Confirm
+     *          dialog.
+     */
+    function installDroppedInFiles(droppedInFiles, xpinstallStrings) {
+      if (droppedInFiles.length == 0)
+        return;
+
+      var dpb = getParamBlock(xpinstallStrings);
+      var ifptr = Cc["@mozilla.org/supports-interface-pointer;1"].
+                  createInstance(Ci.nsISupportsInterfacePointer);
+      ifptr.data = dpb;
+      ifptr.dataIID = Ci.nsIDialogParamBlock;
+      var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+               getService(Ci.nsIWindowWatcher);
+      ww.openWindow(null, URI_XPINSTALL_CONFIRM_DIALOG,
+                    "", "chrome,centerscreen,modal,dialog,titlebar", ifptr);
+      if (!dpb.GetInt(0)) {
+        // User said OK - install items
+        for (var i = 0; i < droppedInFiles.length; ++i) {
+          em.installItemFromFile(droppedInFiles[i].file,
+                                 droppedInFiles[i].location.name);
+          // We are responsible for cleaning up this file
+          droppedInFiles[i].file.remove(false);
+        }
+      }
+      else {
+        for (i = 0; i < droppedInFiles.length; ++i) {
+          // We are responsible for cleaning up this file
+          droppedInFiles[i].file.remove(false);
+        }
+      }
+    }
+
+    var isDirty = false;
+    var ignoreMTimeChanges = getPref("getBoolPref", PREF_EM_IGNOREMTIMECHANGES,
+                                     false);
+    StartupCache.read();
+
+    // Array of objects with 'location' and 'id' properties to maybe install.
+    var newItems = [];
+
+    var droppedInFiles = [];
+    var xpinstallStrings = [];
+
+    // Enumerate over the install locations from low to high priority.  The
+    // enumeration returned is pre-sorted.
+    var installLocations = this.installLocations;
+    while (installLocations.hasMoreElements()) {
+      var location = installLocations.getNext().QueryInterface(Ci.nsIInstallLocation);
+
+      // Hash the set of items actually held by the Install Location.
+      var actualItems = { };
+      var entries = location.itemLocations;
+      while (true) {
+        var entry = entries.nextFile;
+        if (!entry)
+          break;
+
+        // Is this location a valid item? It must be a directory, and contain
+        // an install.rdf manifest:
+        if (entry.isDirectory()) {
+          var installRDF = entry.clone();
+          installRDF.append(FILE_INSTALL_MANIFEST);
+
+          var id = location.getIDForLocation(entry);
+          if (!id || (!installRDF.exists() &&
+                      !location.itemIsManagedIndependently(id)))
+            continue;
+
+          actualItems[id] = entry;
+        }
+        else {
+          // Check to see if this file is a XPI/JAR dropped into this dir
+          // by the user, installing it if necessary. We do this here rather
+          // than separately in |_finishOperations| because I don't want to
+          // walk these lists multiple times on every startup.
+          var item = this._getItemForDroppedFile(entry, location);
+          if (item) {
+            var prettyName = "";
+            try {
+              var zipReader = getZipReaderForFile(entry);
+              zipReader.QueryInterface(Ci.nsIJAR);
+              var principal = zipReader.getCertificatePrincipal(null);
+              if (principal && principal.hasCertificate) {
+                if (verifyZipSigning(zipReader, principal)) {
+                  var x509 = principal.certificate;
+                  if (x509 instanceof Ci.nsIX509Cert && x509.commonName.length > 0)
+                    prettyName = x509.commonName;
+                  else
+                    prettyName = principal.prettyName;
+                }
+                else {
+                  // The xpi isn't correctly signed, don't offer to install.
+                  LOG("Ignoring " + entry.path + " as it is not correctly signed.");
+                  zipReader.close();
+                  entry.remove(true);
+                  continue;
+                }
+              }
+            }
+            catch (e) { }
+            if (zipReader)
+              zipReader.close();
+            droppedInFiles.push({ file: entry, location: location });
+            xpinstallStrings = xpinstallStrings.concat([item.name,
+                                                        getURLSpecFromFile(entry),
+                                                        item.iconURL,
+                                                        prettyName]);
+            isDirty = true;
+          }
+        }
+      }
+
+      if (location.name in StartupCache.entries) {
+        // Look for items that have been uninstalled by removing their directory.
+        for (var id in StartupCache.entries[location.name]) {
+          if (!StartupCache.entries[location.name] ||
+              !StartupCache.entries[location.name][id])
+            continue;
+
+          // Force _finishOperations to run if we have enabled or disabled items.
+          // XXXdarin this should be unnecessary now that we check
+          // PendingOperations.size in start()
+          if (StartupCache.entries[location.name][id].op == OP_NEEDS_ENABLE ||
+              StartupCache.entries[location.name][id].op == OP_NEEDS_DISABLE)
+            isDirty = true;
+
+          if (!(id in actualItems) &&
+              StartupCache.entries[location.name][id].op != OP_NEEDS_UNINSTALL &&
+              StartupCache.entries[location.name][id].op != OP_NEEDS_INSTALL &&
+              StartupCache.entries[location.name][id].op != OP_NEEDS_UPGRADE) {
+            // We have an entry for this id in the Extensions database, for this
+            // install location, but it no longer exists in the Install Location.
+            // We can infer from this that the item has been removed, so uninstall
+            // it properly.
+            if (canUse(id, location)) {
+              LOG("Item Uninstalled via file removal from: " + StartupCache.entries[location.name][id].descriptor +
+                  " Item ID: " + id + " Location Key: " + location.name + ", uninstalling item.");
+
+              // Load the Extensions Datasource and force this item into the visible
+              // items list if it is not already. This allows us to handle the case
+              // where there is an entry for an item in the Startup Cache but not
+              // in the extensions.rdf file - in that case the item will not be in
+              // the visible list and calls to |getInstallLocation| will mysteriously
+              // fail.
+              this.datasource.updateVisibleList(id, location.name, false);
+              this.uninstallItem(id);
+              isDirty = true;
+            }
+          }
+          else if (!ignoreMTimeChanges) {
+            // Look for items whose mtime has changed, and as such we can assume
+            // they have been "upgraded".
+            var lf = { path: StartupCache.entries[location.name][id].descriptor };
+            try {
+               lf = getFileFromDescriptor(StartupCache.entries[location.name][id].descriptor, location);
+            }
+            catch (e) { }
+
+            if (lf.exists && lf.exists()) {
+              var actualMTime = Math.floor(lf.lastModifiedTime / 1000);
+              if (actualMTime != StartupCache.entries[location.name][id].mtime) {
+                LOG("Item Location path changed: " + lf.path + " Item ID: " +
+                    id + " Location Key: " + location.name + ", attempting to upgrade item...");
+                if (canUse(id, location)) {
+                  this.installItem(id, location,
+                                   function(installManifest, id, location, type) {
+                                     em._upgradeItem(installManifest, id, location,
+                                                     type);
+                                   });
+                  isDirty = true;
+                }
+              }
+            }
+            else {
+              isDirty = true;
+              LOG("Install Location returned a missing or malformed item path! " +
+                  "Item Path: " + lf.path + ", Location Key: " + location.name +
+                  " Item ID: " + id);
+              if (canUse(id, location)) {
+                // Load the Extensions Datasource and force this item into the visible
+                // items list if it is not already. This allows us to handle the case
+                // where there is an entry for an item in the Startup Cache but not
+                // in the extensions.rdf file - in that case the item will not be in
+                // the visible list and calls to |getInstallLocation| will mysteriously
+                // fail.
+                this.datasource.updateVisibleList(id, location.name, false);
+                this.uninstallItem(id);
+              }
+            }
+          }
+        }
+      }
+
+      // Look for items that have been installed by appearing in the location.
+      for (var id in actualItems) {
+        if (!(location.name in StartupCache.entries) ||
+            !(id in StartupCache.entries[location.name]) ||
+            !StartupCache.entries[location.name][id]) {
+          // Remember that we've seen this item
+          StartupCache.put(location, id, OP_NONE, true);
+          // Push it on the stack of items to maybe install later
+          newItems.push({location: location, id: id});
+        }
+      }
+    }
+
+    // Process any newly discovered items.  We do this here instead of in the
+    // previous loop so that we can be sure that we have a fully populated
+    // StartupCache.
+    for (var i = 0; i < newItems.length; ++i) {
+      var id = newItems[i].id;
+      var location = newItems[i].location;
+      if (canUse(id, location)) {
+        LOG("Item Installed via directory addition to Install Location: " +
+            location.name + " Item ID: " + id + ", attempting to register...");
+        this.installItem(id, location,
+                         function(installManifest, id, location, type) {
+                           em._configureForthcomingItem(installManifest, id, location,
+                                                        type);
+                         });
+        // Disable add-ons on install when the InstallDisabled file exists.
+        // This is so Talkback will be disabled on a subset of installs.
+        var installDisabled = location.getItemFile(id, "InstallDisabled");
+        if (installDisabled.exists())
+          em.disableItem(id);
+        isDirty = true;
+      }
+    }
+
+    // Ask the user if they want to install the dropped items, for security
+    // purposes.
+    installDroppedInFiles(droppedInFiles, xpinstallStrings);
+
+    return isDirty;
+  },
+
+  _checkForUncoveredItem: function EM__checkForUncoveredItem(id) {
+    var ds = this.datasource;
+    var oldLocation = this.getInstallLocation(id);
+    var newLocations = [];
+    for (var locationKey in StartupCache.entries) {
+      var location = InstallLocations.get(locationKey);
+      if (id in StartupCache.entries[locationKey] &&
+          location.priority > oldLocation.priority)
+        newLocations.push(location);
+    }
+    newLocations.sort(function(a, b) { return b.priority - a.priority; });
+    if (newLocations.length > 0) {
+      for (var i = 0; i < newLocations.length; ++i) {
+        // Check to see that the item at the location exists
+        var installRDF = newLocations[i].getItemFile(id, FILE_INSTALL_MANIFEST);
+        if (installRDF.exists()) {
+          // Update the visible item cache so that |_finalizeUpgrade| is properly
+          // called from |_finishOperations|
+          var name = newLocations[i].name;
+          ds.updateVisibleList(id, name, true);
+          PendingOperations.addItem(OP_NEEDS_UPGRADE,
+                                    { locationKey: name, id: id });
+          PendingOperations.addItem(OP_NEEDS_INSTALL,
+                                    { locationKey: name, id: id });
+          break;
+        }
+        else {
+          // If no item exists at the location specified, remove this item
+          // from the visible items list and check again.
+          StartupCache.clearEntry(newLocations[i], id);
+          ds.updateVisibleList(id, null, true);
+        }
+      }
+    }
+    else
+      ds.updateVisibleList(id, null, true);
+  },
+
+  /**
+   * Finish up pending operations - perform upgrades, installs, enables/disables,
+   * uninstalls etc.
+   * @returns true if actions were performed that require a restart, false
+   *          otherwise.
+   */
+  _finishOperations: function EM__finishOperations() {
+    try {
+      // Stuff has changed, load the Extensions datasource in all its RDFey
+      // glory.
+      var ds = this.datasource;
+      var updatedTargetAppInfos = [];
+
+      var needsRestart = false;
+      var upgrades = [];
+      var newAddons = [];
+      var addons = getPref("getCharPref", PREF_EM_NEW_ADDONS_LIST, "");
+      if (addons != "")
+        newAddons = addons.split(",");
+      do {
+        // Enable and disable during startup so items that are changed in the
+        // ui can be reset to a no-op.
+        // Look for extensions that need to be enabled.
+        var items = PendingOperations.getOperations(OP_NEEDS_ENABLE);
+        for (var i = items.length - 1; i >= 0; --i) {
+          var id = items[i].id;
+          var installLocation = this.getInstallLocation(id);
+          StartupCache.put(installLocation, id, OP_NONE, true);
+          PendingOperations.clearItem(OP_NEEDS_ENABLE, id);
+          needsRestart = true;
+        }
+        PendingOperations.clearItems(OP_NEEDS_ENABLE);
+
+        // Look for extensions that need to be disabled.
+        items = PendingOperations.getOperations(OP_NEEDS_DISABLE);
+        for (i = items.length - 1; i >= 0; --i) {
+          id = items[i].id;
+          installLocation = this.getInstallLocation(id);
+          StartupCache.put(installLocation, id, OP_NONE, true);
+          PendingOperations.clearItem(OP_NEEDS_DISABLE, id);
+          needsRestart = true;
+        }
+        PendingOperations.clearItems(OP_NEEDS_DISABLE);
+
+        // Look for extensions that need to be upgraded. The process here is to
+        // uninstall the old version of the extension first, then install the
+        // new version in its place.
+        items = PendingOperations.getOperations(OP_NEEDS_UPGRADE);
+        for (i = items.length - 1; i >= 0; --i) {
+          id = items[i].id;
+          var newLocation = InstallLocations.get(items[i].locationKey);
+          // check if there is updated app compatibility info
+          var newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
+          if (newTargetAppInfo)
+            updatedTargetAppInfos.push(newTargetAppInfo);
+          this._finalizeUpgrade(id, newLocation);
+          upgrades.push(id);
+        }
+        PendingOperations.clearItems(OP_NEEDS_UPGRADE);
+
+        // Install items
+        items = PendingOperations.getOperations(OP_NEEDS_INSTALL);
+        for (i = items.length - 1; i >= 0; --i) {
+          needsRestart = true;
+          id = items[i].id;
+          // check if there is updated app compatibility info
+          newTargetAppInfo = ds.getUpdatedTargetAppInfo(id);
+          if (newTargetAppInfo)
+            updatedTargetAppInfos.push(newTargetAppInfo);
+          this._finalizeInstall(id, null);
+          if (upgrades.indexOf(id) < 0 && newAddons.indexOf(id) < 0)
+            newAddons.push(id);
+        }
+        PendingOperations.clearItems(OP_NEEDS_INSTALL);
+
+        // Look for extensions that need to be removed. This MUST be done after
+        // the install operations since extensions to be installed may have to be
+        // uninstalled if there are errors during the installation process!
+        items = PendingOperations.getOperations(OP_NEEDS_UNINSTALL);
+        for (i = items.length - 1; i >= 0; --i) {
+          id = items[i].id;
+          this._finalizeUninstall(id);
+          this._checkForUncoveredItem(id);
+          needsRestart = true;
+          var pos = newAddons.indexOf(id);
+          if (pos >= 0)
+            newAddons.splice(pos, 1);
+        }
+        PendingOperations.clearItems(OP_NEEDS_UNINSTALL);
+
+        // When there have been operations and all operations have completed.
+        if (PendingOperations.size == 0) {
+          // If there is updated app compatibility info update the datasource.
+          for (i = 0; i < updatedTargetAppInfos.length; ++i)
+            ds.setTargetApplicationInfo(updatedTargetAppInfos[i].id,
+                                        updatedTargetAppInfos[i].targetAppID,
+                                        updatedTargetAppInfos[i].minVersion,
+                                        updatedTargetAppInfos[i].maxVersion,
+                                        null);
+
+          // Enumerate all items
+          var ctr = getContainer(ds, ds._itemRoot);
+          var elements = ctr.GetElements();
+          while (elements.hasMoreElements()) {
+            var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
+
+            // Ensure appDisabled is in the correct state.
+            id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
+            if (this._isUsableItem(id))
+              ds.setItemProperty(id, EM_R("appDisabled"), null);
+            else
+              ds.setItemProperty(id, EM_R("appDisabled"), EM_L("true"));
+
+            // userDisabled is set based on its value being OP_NEEDS_ENABLE or
+            // OP_NEEDS_DISABLE. This allows us to have an item to be enabled
+            // by the app and disabled by the user during a single restart.
+            var value = stringData(ds.GetTarget(itemResource, EM_R("userDisabled"), true));
+            if (value == OP_NEEDS_ENABLE)
+              ds.setItemProperty(id, EM_R("userDisabled"), null);
+            else if (value == OP_NEEDS_DISABLE)
+              ds.setItemProperty(id, EM_R("userDisabled"), EM_L("true"));
+          }
+        }
+      }
+      while (PendingOperations.size > 0);
+
+      // If no additional restart is required, it implies that there are
+      // no new components that need registering so we can inform the app
+      // not to do any extra startup checking next time round.
+      this._updateManifests(needsRestart);
+
+      // Remember the list of add-ons that were installed this time around
+      // unless this was a new profile.
+      if (!gFirstRun && newAddons.length > 0)
+        gPref.setCharPref(PREF_EM_NEW_ADDONS_LIST, newAddons.join(","));
+    }
+    catch (e) {
+      ERROR("ExtensionManager:_finishOperations - failure, catching exception - lineno: " +
+            e.lineNumber + " - file: " + e.fileName + " - " + e);
+    }
+    return needsRestart;
+  },
+
+  /**
+   * Checks to see if there are items that are incompatible with this version
+   * of the application, disables them to prevent incompatibility problems and
+   * invokes the Update Wizard to look for newer versions.
+   * @returns true if there were incompatible items installed and disabled, and
+   *          the application must now be restarted to reinitialize XPCOM,
+   *          false otherwise.
+   */
+  checkForMismatches: function EM_checkForMismatches() {
+    // Check to see if the version of the application that is being started
+    // now is the same one that was started last time.
+    var currAppVersion = gApp.version;
+    var lastAppVersion = getPref("getCharPref", PREF_EM_LAST_APP_VERSION, "");
+    if (currAppVersion == lastAppVersion)
+      return false;
+    // With a new profile lastAppVersion doesn't exist yet.
+    if (!lastAppVersion) {
+      gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
+      return false;
+    }
+
+    // Block attempts to flush for the entire startup
+    gAllowFlush = false;
+
+    // Make the extensions datasource consistent if it isn't already.
+    var isDirty;
+    [isDirty,] = this._ensureDatasetIntegrity();
+
+    if (this._checkForFileChanges())
+      isDirty = true;
+
+    if (PendingOperations.size != 0)
+      isDirty = true;
+
+    var ds = this.datasource;
+    var inactiveItemIDs = [];
+    var ctr = getContainer(ds, ds._itemRoot);
+    var elements = ctr.GetElements();
+    while (elements.hasMoreElements()) {
+      var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
+      var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
+      var appDisabled = ds.getItemProperty(id, "appDisabled");
+      var userDisabled = ds.getItemProperty(id, "userDisabled")
+      if (appDisabled == "true" || appDisabled == OP_NEEDS_DISABLE ||
+          userDisabled == "true" || userDisabled == OP_NEEDS_DISABLE)
+        inactiveItemIDs.push(id);
+    }
+
+    if (isDirty)
+      this._finishOperations();
+
+    // During app upgrade cleanup invalid entries in the extensions datasource.
+    ds.beginUpdateBatch();
+    var allResources = ds.GetAllResources();
+    while (allResources.hasMoreElements()) {
+      var res = allResources.getNext().QueryInterface(Ci.nsIRDFResource);
+      if (ds.GetTarget(res, EM_R("downloadURL"), true) ||
+          (!ds.GetTarget(res, EM_R("installLocation"), true) &&
+          stringData(ds.GetTarget(res, EM_R("appDisabled"), true)) == "true"))
+        ds.removeDownload(res.Value);
+    }
+    ds.endUpdateBatch();
+
+    var badItems = [];
+    var disabledAddons = [];
+    var allAppManaged = true;
+    elements = ctr.GetElements();
+    while (elements.hasMoreElements()) {
+      var itemResource = elements.getNext().QueryInterface(Ci.nsIRDFResource);
+      var id = stripPrefix(itemResource.Value, PREFIX_ITEM_URI);
+      var location = this.getInstallLocation(id);
+      if (!location) {
+        // Item was in an unknown install location
+        badItems.push(id);
+        continue;
+      }
+
+      if (ds.getItemProperty(id, "appManaged") == "true") {
+        // Force an update of the metadata for appManaged extensions since the
+        // last modified time is not updated for directories on FAT / FAT32
+        // filesystems when software update applies a new version of the app.
+        if (location.name == KEY_APP_GLOBAL) {
+          var installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
+          if (installRDF.exists()) {
+            var metadataDS = getInstallManifest(installRDF);
+            ds.addItemMetadata(id, metadataDS, location);
+            ds.updateProperty(id, "compatible");
+          }
+        }
+      }
+      else if (allAppManaged)
+        allAppManaged = false;
+
+      var properties = {
+        availableUpdateURL: null,
+        availableUpdateVersion: null
+      };
+
+      if (ds.getItemProperty(id, "providesUpdatesSecurely") == "false") {
+        /* It's possible the previous version did not understand updateKeys so
+         * check if we can import one for this addon from its manifest. */
+        installRDF = location.getItemFile(id, FILE_INSTALL_MANIFEST);
+        if (installRDF.exists()) {
+          metadataDS = getInstallManifest(installRDF);
+          var literal = metadataDS.GetTarget(gInstallManifestRoot, EM_R("updateKey"), true);
+          if (literal && literal instanceof Ci.nsIRDFLiteral)
+            ds.setItemProperty(id, EM_R("updateKey"), literal);
+        }
+      }
+
+      // appDisabled is determined by an item being compatible, using secure
+      // updates, satisfying its dependencies, and not being blocklisted
+      if (this._isUsableItem(id)) {
+        if (ds.getItemProperty(id, "appDisabled"))
+          properties.appDisabled = null;
+      }
+      else if (!ds.getItemProperty(id, "appDisabled")) {
+        properties.appDisabled = EM_L("true");
+        disabledAddons.push(id);
+      }
+
+      ds.setItemProperties(id, properties);
+    }
+
+    // Must clean up outside of the loop. Modifying the container while
+    // iterating its contents is bad.
+    for (var i = 0; i < badItems.length; i++) {
+      id = badItems[i];
+      LOG("Item " + id + " was installed in an unknown location, removing.");
+      var disabled = ds.getItemProperty(id, "userDisabled") == "true";
+      // Clean up the datasource
+      ds.removeCorruptItem(id);
+      // Check for any unhidden items.
+      var entries = StartupCache.findEntries(id);
+      if (entries.length > 0) {
+        var newLocation = InstallLocations.get(entries[0].location);
+        for (var j = 1; j < entries.length; j++) {
+          location = InstallLocations.get(entries[j].location);
+          if (newLocation.priority < location.priority)
+            newLocation = location;
+        }
+        LOG("Activating item " + id + " in " + newLocation.name);
+        var em = this;
+        this.installItem(id, newLocation,
+                         function(installManifest, id, location, type) {
+                           em._configureForthcomingItem(installManifest, id, location,
+                                                        type);
+                         });
+        if (disabled)
+          em.disableItem(id);
+      }
+    }
+
+    // Update the manifests to reflect the items that were disabled / enabled.
+    this._updateManifests(true);
+
+    // Determine if we should check for compatibility updates when upgrading if
+    // we have add-ons that aren't managed by the application.
+    if (!allAppManaged && !gFirstRun && disabledAddons.length > 0) {
+      // Should we show a UI or just pass the list via a pref?
+      if (getPref("getBoolPref", PREF_EM_SHOW_MISMATCH_UI, true)) {
+        this._showMismatchWindow(inactiveItemIDs);
+      }
+      else {
+        // Remember the list of add-ons that were disabled this time around
+        gPref.setCharPref(PREF_EM_DISABLED_ADDONS_LIST, disabledAddons.join(","));
+      }
+    } else if (gPref.prefHasUserValue(PREF_EM_DISABLED_ADDONS_LIST)) {
+      // Clear the disabled addons list if necessary
+      gPref.clearUserPref(PREF_EM_DISABLED_ADDONS_LIST);
+    }
+
+    // Finish any pending upgrades from the compatibility update to avoid an
+    // additional restart.
+    if (PendingOperations.size != 0)
+      this._finishOperations();
+
+    // Update the last app version so we don't do this again with this version.
+    gPref.setCharPref(PREF_EM_LAST_APP_VERSION, currAppVersion);
+
+    // Prevent extension update dialog from showing
+    gPref.setBoolPref(PREF_UPDATE_NOTIFYUSER, false);
+
+    // Re-enable flushing and flush anything that was deferred
+    try {
+      gAllowFlush = true;
+      if (gManifestNeedsFlush) {
+        gManifestNeedsFlush = false;
+        this._updateManifests(false);
+      }
+      if (gDSNeedsFlush) {
+        gDSNeedsFlush = false;
+        this.datasource.Flush();
+      }
+    }
+    catch (e) {
+      ERROR("Error flushing caches: " + e);
+    }
+
+    return true;
+  },
+
+  /**
+   * Shows the "Compatibility Updates" UI
+   * @param   items
+   *          an array of item IDs that were not enabled in the previous version
+   *          of the application.
+   */
+  _showMismatchWindow: function EM__showMismatchWindow(items) {
+    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+             getService(Ci.nsIWindowMediator);
+    var wizard = wm.getMostRecentWindow("Update:Wizard");
+    if (wizard)
+      wizard.focus();
+    else {
+      var variant = Cc["@mozilla.org/variant;1"].
+                    createInstance(Ci.nsIWritableVariant);
+      variant.setFromVariant(items);
+      var features = "chrome,centerscreen,dialog,titlebar,modal";
+      // This *must* be modal so as not to break startup! This code is invoked before
+      // the main event loop is initiated (via checkForMismatches).
+      var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+               getService(Ci.nsIWindowWatcher);
+      ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
+    }
+  },
+
+  /**
+   * Write the Extensions List and the Startup Cache
+   * @param   needsRestart
+   *          true if the application needs to restart again, false otherwise.
+   */
+  _updateManifests: function EM__updateManifests(needsRestart) {
+    // During startup we block flushing until the startup operations are all
+    // complete to reduce file accesses that can trigger bug 431065
+    if (gAllowFlush) {
+      // Write the Startup Cache (All Items, visible or not)
+      StartupCache.write();
+      // Write the Extensions Locations Manifest (Visible, enabled items)
+      this._updateExtensionsManifest();
+    }
+    else {
+      gManifestNeedsFlush = true;
+    }
+
+    // Notify nsAppRunner to update the compatibility manifest on next startup
+    this._extensionListChanged = needsRestart;
+  },
+
+  /**
+   * Get a list of items that are currently "active" (turned on) of a specific
+   * type
+   * @param   type
+   *          The nsIUpdateItem type to return a list of items of
+   * @returns An array of active items of the specified type.
+   */
+  _getActiveItems: function EM__getActiveItems(type) {
+    var allItems = this.getItemList(type);
+    var activeItems = [];
+    var ds = this.datasource;
+    for (var i = 0; i < allItems.length; ++i) {
+      var item = allItems[i];
+
+      var installLocation = this.getInstallLocation(item.id);
+      // An entry with an invalid install location is not active.
+      if (!installLocation)
+        continue;
+      // An item entry is valid only if it is not disabled, not about to
+      // be disabled, and not about to be uninstalled.
+      if (installLocation.name in StartupCache.entries &&
+          item.id in StartupCache.entries[installLocation.name] &&
+          StartupCache.entries[installLocation.name][item.id]) {
+        var op = StartupCache.entries[installLocation.name][item.id].op;
+        if (op == OP_NEEDS_INSTALL || op == OP_NEEDS_UPGRADE ||
+            op == OP_NEEDS_UNINSTALL || op == OP_NEEDS_DISABLE)
+          continue;
+      }
+      // Suppress items that have been disabled by the user or the app.
+      if (ds.getItemProperty(item.id, "isDisabled") != "true")
+        activeItems.push({ id: item.id, version: item.version,
+                           location: installLocation });
+    }
+
+    return activeItems;
+  },
+
+  /**
+   * Write the Extensions List
+   */
+  _updateExtensionsManifest: function EM__updateExtensionsManifest() {
+    // When an operation is performed that requires a component re-registration
+    // (extension enabled/disabled, installed, uninstalled), we must write the
+    // set of paths where extensions live so that the startup system can determine
+    // where additional components, preferences, chrome manifests etc live.
+    //
+    // To do this we obtain a list of active extensions and themes and write
+    // these to the extensions.ini file in the profile directory.
+    var validExtensions = this._getActiveItems(Ci.nsIUpdateItem.TYPE_ANY -
+                                               Ci.nsIUpdateItem.TYPE_THEME);
+    var validThemes     = this._getActiveItems(Ci.nsIUpdateItem.TYPE_THEME);
+
+    var extensionsLocationsFile = FileUtils.getFile(KEY_PROFILEDIR,
+                                                    [FILE_EXTENSION_MANIFEST]);
+    var fos = FileUtils.openSafeFileOutputStream(extensionsLocationsFile);
+
+    var enabledItems = [];
+    var extensionSectionHeader = "[ExtensionDirs]\r\n";
+    fos.write(extensionSectionHeader, extensionSectionHeader.length);
+    for (var i = 0; i < validExtensions.length; ++i) {
+      var e = validExtensions[i];
+      var itemLocation = e.location.getItemLocation(e.id).QueryInterface(Ci.nsILocalFile);
+      var descriptor = getAbsoluteDescriptor(itemLocation);
+      var line = "Extension" + i + "=" + descriptor + "\r\n";
+      fos.write(line, line.length);
+      enabledItems.push(e.id + ":" + e.version);
+    }
+
+    var themeSectionHeader = "[ThemeDirs]\r\n";
+    fos.write(themeSectionHeader, themeSectionHeader.length);
+    for (i = 0; i < validThemes.length; ++i) {
+      var e = validThemes[i];
+      var itemLocation = e.location.getItemLocation(e.id).QueryInterface(Ci.nsILocalFile);
+      var descriptor = getAbsoluteDescriptor(itemLocation);
+      var line = "Extension" + i + "=" + descriptor + "\r\n";
+      fos.write(line, line.length);
+      enabledItems.push(e.id + ":" + e.version);
+    }
+
+    FileUtils.closeSafeFileOutputStream(fos);
+
+    // Cache the enabled list for annotating the crash report subsequently
+    gPref.setCharPref(PREF_EM_ENABLED_ITEMS, enabledItems.join(","));
+  },
+
+  /**
+   * Say whether or not the Extension List has changed (and thus whether or not
+   * the system will have to restart the next time it is started).
+   * @param   val
+   *          true if the Extension List has changed, false otherwise.
+   * @returns |val|
+   */
+  set _extensionListChanged(val) {
+    // When an extension has an operation perform on it (e.g. install, upgrade,
+    // disable, etc.) we are responsible for writing this information to 
+    // compatibility.ini, and nsAppRunner is responsible for checking this on 
+    // restart. At some point it may make sense to be able to cancel a 
+    // registration but for now we only create the file.
+    if (val) {
+      let XRE = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+      XRE.invalidateCachesOnRestart();
+    }
+    return val;
+  },
+
+  /**
+   * Gathers data about an item specified by the supplied Install Manifest
+   * and determines whether or not it can be installed as-is. It makes this
+   * determination by validating the item's GUID, Version, and determining
+   * if it is compatible with this application.
+   * @param   installManifest
+   *          A nsIRDFDataSource representing the Install Manifest of the
+   *          item to be installed.
+   * @return  A JS Object with the following properties:
+   *          "id"       The GUID of the Item being installed.
+   *          "version"  The Version string of the Item being installed.
+   *          "name"     The Name of the Item being installed.
+   *          "type"     The nsIUpdateItem type of the Item being installed.
+   *          "targetApps" An array of TargetApplication Info Objects
+   *                     with "id", "minVersion" and "maxVersion" properties,
+   *                     representing applications targeted by this item.
+   *          "error"    The result code:
+   *                     INSTALLERROR_SUCCESS
+   *                       no error, item can be installed
+   *                     INSTALLERROR_INVALID_GUID
+   *                       error, GUID is not well-formed
+   *                     INSTALLERROR_INVALID_VERSION
+   *                       error, Version is not well-formed
+   *                     INSTALLERROR_INCOMPATIBLE_VERSION
+   *                       error, item is not compatible with this version
+   *                       of the application.
+   *                     INSTALLERROR_INCOMPATIBLE_PLATFORM
+   *                       error, item is not compatible with the operating
+   *                       system or ABI the application was built for.
+   *                     INSTALLERROR_INSECURE_UPDATE
+   *                       error, item has no secure method of providing updates
+   *                     INSTALLERROR_BLOCKLISTED
+   *                       error, item is blocklisted
+   */
+  _getInstallData: function EM__getInstallData(installManifest) {
+    var installData = { id          : "",
+                        version     : "",
+                        name        : "",
+                        type        : 0,
+                        error       : INSTALLERROR_SUCCESS,
+                        targetApps  : [],
+                        updateURL   : "",
+                        updateKey   : "",
+                        currentApp  : null };
+
+    // Fetch properties from the Install Manifest
+    installData.id       = getManifestProperty(installManifest, "id");
+    installData.version  = getManifestProperty(installManifest, "version");
+    installData.name     = getManifestProperty(installManifest, "name");
+    installData.type     = getAddonTypeFromInstallManifest(installManifest);
+    installData.updateURL= getManifestProperty(installManifest, "updateURL");
+    installData.updateKey= getManifestProperty(installManifest, "updateKey");
+
+    /**
+     * Reads a property off a Target Application resource
+     * @param   resource
+     *          The RDF Resource for a Target Application
+     * @param   property
+     *          The property (less EM_NS) to read
+     * @returns The string literal value of the property.
+     */
+    function readTAProperty(resource, property) {
+      return stringData(installManifest.GetTarget(resource, EM_R(property), true));
+    }
+
+    var targetApps = installManifest.GetTargets(gInstallManifestRoot,
+                                                EM_R("targetApplication"),
+                                                true);
+    while (targetApps.hasMoreElements()) {
+      var targetApp = targetApps.getNext();
+      if (targetApp instanceof Ci.nsIRDFResource) {
+        try {
+          var data = { id        : readTAProperty(targetApp, "id"),
+                       minVersion: readTAProperty(targetApp, "minVersion"),
+                       maxVersion: readTAProperty(targetApp, "maxVersion") };
+          installData.targetApps.push(data);
+          if ((data.id == gApp.ID) ||
+              (data.id == TOOLKIT_ID) && !installData.currentApp)
+            installData.currentApp = data;
+        }
+        catch (e) {
+          continue;
+        }
+      }
+    }
+
+    // If the item specifies one or more target platforms, make sure our OS/ABI
+    // combination is in the list - otherwise, refuse to install the item.
+    var targetPlatforms = null;
+    try {
+      targetPlatforms = installManifest.GetTargets(gInstallManifestRoot,
+                                                   EM_R("targetPlatform"),
+                                                   true);
+    } ca