Bug 553169: Implement the basic extension manager API and platform integration. r=robstrong
authorDave Townsend <dtownsend@oxymoronical.com>
Thu, 29 Apr 2010 13:11:23 -0700
changeset 41549 f746d2eca19927e140dbe5fb3eb955d503e5b64b
parent 41548 5c7b79118121ff897ce0a2f247d45805d7655f97
child 41550 e7d28ca95cd121e871c656e36390821e58df767e
push idunknown
push userunknown
push dateunknown
reviewersrobstrong
bugs553169
milestone1.9.3a5pre
Bug 553169: Implement the basic extension manager API and platform integration. r=robstrong
browser/installer/package-manifest.in
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/Makefile.in
toolkit/mozapps/extensions/addonManager.js
toolkit/mozapps/extensions/amIWebInstaller.idl
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -290,17 +290,17 @@
 @BINPATH@/components/nsFilePicker.js
 #endif
 @BINPATH@/components/nsHelperAppDlg.js
 @BINPATH@/components/nsDownloadManagerUI.js
 @BINPATH@/components/nsProxyAutoConfig.js
 @BINPATH@/components/NetworkGeolocationProvider.js
 @BINPATH@/components/GPSDGeolocationProvider.js
 @BINPATH@/components/nsSidebar.js
-@BINPATH@/components/nsExtensionManager.js
+@BINPATH@/components/addonManager.js
 @BINPATH@/components/nsBlocklistService.js
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
 #endif
 @BINPATH@/components/nsUpdateTimerManager.js
 @BINPATH@/components/pluginGlue.js
 @BINPATH@/components/nsSessionStartup.js
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -0,0 +1,877 @@
+/*
+# ***** 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
+# the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# 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;
+
+const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
+
+// A list of providers to load by default
+const PROVIDERS = [
+  "resource://gre/modules/XPIProvider.jsm",
+  "resource://gre/modules/LightweightThemeManager.jsm"
+];
+
+/**
+ * Logs a debugging message.
+ *
+ * @param   str
+ *          The string to log
+ */
+function LOG(str) {
+  dump("*** addons.manager: " + str + "\n");
+}
+
+/**
+ * Logs a warning message.
+ *
+ * @param   str
+ *          The string to log
+ */
+function WARN(str) {
+  LOG(str);
+}
+
+/**
+ * Logs an error message.
+ *
+ * @param   str
+ *          The string to log
+ */
+function ERROR(str) {
+  LOG(str);
+}
+
+/**
+ * Calls a callback method consuming any thrown exception. Any parameters after
+ * the callback parameter will be passed to the callback.
+ *
+ * @param   callback
+ *          The callback method to call
+ */
+function safeCall(callback) {
+  var args = Array.slice(arguments, 1);
+
+  try {
+    callback.apply(null, args);
+  }
+  catch (e) {
+    WARN("Exception calling callback: " + e);
+  }
+}
+
+/**
+ * Calls a method on a provider if it exists and consumes any thrown exception.
+ * Any parameters after the dflt parameter are passed to the provider's method.
+ *
+ * @param   provider
+ *          The provider to call
+ * @param   method
+ *          The method name to call
+ * @param   dflt
+ *          A default return value if the provider does not implement the named
+ *          method or throws an error.
+ * @return  the return value from the provider or dflt if the provider does not
+ *          implement method or throws an error
+ */
+function callProvider(provider, method, dflt) {
+  if (!(method in provider))
+    return dflt;
+
+  var args = Array.slice(arguments, 3);
+
+  try {
+    return provider[method].apply(provider, args);
+  }
+  catch (e) {
+    ERROR("Exception calling provider." + method + ": " + e);
+    return dflt;
+  }
+}
+
+/**
+ * A helper class to repeatedly call a listener with each object in an array
+ * optionally checking whether the object has a method in it.
+ *
+ * @param   objects
+ *          The array of objects to iterate through
+ * @param   method
+ *          An optional method name, if not null any objects without this method
+ *          will not be passed to the listener
+ * @param   listener
+ *          A listener implementing nextObject and noMoreObjects methods. The
+ *          former will be called with the AsyncObjectCaller as the first
+ *          parameter and the object as the second. noMoreObjects will be passed
+ *          just the AsyncObjectCaller
+ */
+function AsyncObjectCaller(objects, method, listener) {
+  this.objects = objects.slice(0);
+  this.method = method;
+  this.listener = listener;
+
+  this.callNext();
+}
+
+AsyncObjectCaller.prototype = {
+  objects: null,
+  method: null,
+  listener: null,
+
+  /**
+   * Passes the next object to the listener or calls noMoreObjects if there
+   * are none left.
+   */
+  callNext: function AOC_callNext() {
+    if (this.objects.length == 0) {
+      this.listener.noMoreObjects(this);
+      return;
+    }
+
+    let object = this.objects.shift();
+    if (!this.method || this.method in object)
+      this.listener.nextObject(this, object);
+    else
+      this.callNext();
+  }
+};
+
+/**
+ * This is the real manager, kept here rather than in AddonManager to keep its
+ * contents hidden from API users.
+ */
+var AddonManagerInternal = {
+  installListeners: null,
+  addonListeners: null,
+  providers: [],
+  started: false,
+
+  /**
+   * Initializes the AddonManager, loading any known providers and initializing
+   * them. 
+   */
+  startup: function AMI_startup() {
+    if (this.started)
+      return;
+
+    this.installListeners = [];
+    this.addonListeners = [];
+
+    let appChanged = true;
+
+    try {
+      appChanged = Services.appinfo.version !=
+                   Services.prefs.getCharPref("extensions.lastAppVersion");
+    }
+    catch (e) { }
+
+    if (appChanged) {
+      LOG("Application has been upgraded");
+      Services.prefs.setCharPref("extensions.lastAppVersion",
+                                 Services.appinfo.version);
+    }
+
+    // Ensure all default providers have had a chance to register themselves
+    PROVIDERS.forEach(function(url) {
+      Components.utils.import(url, {});
+    });
+
+    let needsRestart = false;
+    this.providers.forEach(function(provider) {
+      callProvider(provider, "startup");
+      if (callProvider(provider, "checkForChanges", false, appChanged))
+        needsRestart = true;
+    });
+    this.started = true;
+
+    // Flag to the platform that a restart is necessary
+    if (needsRestart) {
+      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
+                       getService(Ci.nsIAppStartup2);
+      appStartup.needsRestart = needsRestart;
+    }
+  },
+
+  /**
+   * Registers a new AddonProvider.
+   *
+   * @param   provider
+   *          The provider to register
+   */
+  registerProvider: function AMI_registerProvider(provider) {
+    this.providers.push(provider);
+
+    // If we're registering after startup call this provider's startup.
+    if (this.started)
+      callProvider(provider, "startup");
+  },
+
+  /**
+   * Shuts down the addon manager and all registered providers, this must clean
+   * up everything in order for automated tests to fake restarts.
+   */
+  shutdown: function AM_shutdown() {
+    this.providers.forEach(function(provider) {
+      callProvider(provider, "shutdown");
+    });
+
+    this.installListeners = null;
+    this.addonListeners = null;
+    this.started = false;
+  },
+
+  /**
+   * Performs a background update check by starting an update for all add-ons
+   * that can be updated.
+   */
+  backgroundUpdateCheck: function AMI_backgroundUpdateCheck() {
+    if (!Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED))
+      return;
+
+    this.getAddonsByTypes(null, function getAddonsCallback(addons) {
+      addons.forEach(function BUC_forEachCallback(addon) {
+        if (addon.permissions & AddonManager.PERM_CAN_UPGRADE) {
+          addon.findUpdates({
+            onUpdateAvailable: function BUC_onUpdateAvailable(addon, install) {
+              install.install();
+            }
+          }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+        }
+      });
+    });
+  },
+
+  /**
+   * Calls all registered InstallListeners with an event. Any parameters after
+   * the extraListeners parameter are passed to the listener.
+   *
+   * @param   method
+   *          The method on the listeners to call
+   * @param   extraListeners
+   *          An array of extra InstallListeners to also call
+   * @return  false if any of the listeners returned false, true otherwise
+   */
+  callInstallListeners: function AMI_callInstallListeners(method, extraListeners) {
+    let result = true;
+    let listeners = this.installListeners;
+    if (extraListeners)
+      listeners = extraListeners.concat(listeners);
+    let args = Array.slice(arguments, 2);
+
+    listeners.forEach(function(listener) {
+      try {
+        if (method in listener) {
+          if (listener[method].apply(listener, args) === false)
+            result = false;
+        }
+      }
+      catch (e) {
+        WARN("InstallListener threw exception when calling " + method + ": " + e);
+      }
+    });
+    return result;
+  },
+
+  /**
+   * Calls all registered AddonListeners with an event. Any parameters after
+   * the method parameter are passed to the listener.
+   *
+   * @param   method
+   *          The method on the listeners to call
+   */
+  callAddonListeners: function AMI_callAddonListeners(method) {
+    var args = Array.slice(arguments, 1);
+    this.addonListeners.forEach(function(listener) {
+      try {
+        if (method in listener)
+          listener[method].apply(listener, args);
+      }
+      catch (e) {
+        WARN("AddonListener threw exception when calling " + method + ": " + e);
+      }
+    });
+  },
+
+  /**
+   * Notifies all providers that an add-on has been enabled when that type of
+   * add-on only supports a single add-on being enabled at a time. This allows
+   * the providers to disable theirs if necessary.
+   *
+   * @param   id
+   *          The id of the enabled add-on
+   * @param   type
+   *          The type of the enabled add-on
+   * @param   pendingRestart
+   *          A boolean indicating if the change will only take place the next
+   *          time the application is restarted
+   */
+  notifyAddonChanged: function AMI_notifyAddonChanged(id, type, pendingRestart) {
+    this.providers.forEach(function(provider) {
+      callProvider(provider, "addonChanged", null, id, type, pendingRestart);
+    });
+  },
+
+  /**
+   * Asynchronously gets an AddonInstall for a URL.
+   *
+   * @param   url
+   *          The url the add-on is located at
+   * @param   callback
+   *          A callback to pass the AddonInstall to
+   * @param   mimetype
+   *          The mimetype of the add-on
+   * @param   hash
+   *          An optional hash of the add-on
+   * @param   name
+   *          An optional placeholder name while the add-on is being downloaded
+   * @param   iconURL
+   *          An optional placeholder icon URL while the add-on is being downloaded
+   * @param   version
+   *          An optional placeholder version while the add-on is being downloaded
+   * @param   loadgroup
+   *          An optional nsILoadGroup to associate any network requests with
+   * @throws  if the url, callback or mimetype arguments are not specified
+   */
+  getInstallForURL: function AMI_getInstallForURL(url, callback, mimetype, hash,
+                                                  name, iconURL, version,
+                                                  loadgroup) {
+    if (!url || !mimetype || !callback)
+      throw new TypeError("Invalid arguments");
+
+    for (let i = 0; i < this.providers.length; i++) {
+      if (callProvider(this.providers[i], "supportsMimetype", false, mimetype)) {
+        callProvider(this.providers[i], "getInstallForURL", null,
+                     url, hash, name, iconURL, version, loadgroup,
+                     function(install) {
+          safeCall(callback, install);
+        });
+        return;
+      }
+    }
+    safeCall(callback, null);
+  },
+
+  /**
+   * Asynchronously gets an AddonInstall for an nsIFile.
+   *
+   * @param   file
+   *          the nsIFile where the add-on is located
+   * @param   callback
+   *          A callback to pass the AddonInstall to
+   * @param   mimetype
+   *          An optional mimetype hint for the add-on
+   * @throws  if the file or callback arguments are not specified
+   */
+  getInstallForFile: function AMI_getInstallForFile(file, callback, mimetype) {
+    if (!file || !callback)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    new AsyncObjectCaller(this.providers, "getInstallForFile", {
+      nextObject: function(caller, provider) {
+        callProvider(provider, "getInstallForFile", null, file,
+                     function(install) {
+          if (install)
+            safeCall(callback, install);
+          else
+            caller.callNext();
+        });
+      },
+
+      noMoreObjects: function(caller) {
+        safeCall(callback, null);
+      }
+    });
+  },
+
+  /**
+   * Asynchronously gets all current AddonInstalls optionally limiting to a list
+   * of types.
+   *
+   * @param   types
+   *          An optional array of types to retrieve. Each type is a string name
+   * @param   callback
+   *          A callback which will be passed an array of AddonInstalls
+   * @throws  if the callback argument is not specified
+   */
+  getInstalls: function AMI_getInstalls(types, callback) {
+    if (!callback)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let installs = [];
+
+    new AsyncObjectCaller(this.providers, "getInstalls", {
+      nextObject: function(caller, provider) {
+        callProvider(provider, "getInstalls", null, types,
+                     function(providerInstalls) {
+          installs = installs.concat(providerInstalls);
+          caller.callNext();
+        });
+      },
+
+      noMoreObjects: function(caller) {
+        safeCall(callback, installs);
+      }
+    });
+  },
+
+  /**
+   * Checks whether installation is enabled for a particular mimetype.
+   *
+   * @param   mimetype
+   *          The mimetype to check
+   * @return  true if installation is enabled for the mimetype
+   */
+  isInstallEnabled: function AMI_isInstallEnabled(mimetype) {
+    for (let i = 0; i < this.providers.length; i++) {
+      if (callProvider(this.providers[i], "supportsMimetype", false, mimetype) &&
+          callProvider(this.providers[i], "isInstallEnabled"))
+        return true;
+    }
+    return false;
+  },
+
+  /**
+   * Checks whether a particular source is allowed to install add-ons of a
+   * given mimetype.
+   *
+   * @param   mimetype
+   *          The mimetype of the add-on
+   * @param   uri
+   *          The uri of the source, may be null
+   * @return  true if the source is allowed to install this mimetype
+   */
+  isInstallAllowed: function AMI_isInstallAllowed(mimetype, uri) {
+    for (let i = 0; i < this.providers.length; i++) {
+      if (callProvider(this.providers[i], "supportsMimetype", false, mimetype) &&
+          callProvider(this.providers[i], "isInstallAllowed", null, uri))
+        return true;
+    }
+  },
+
+  /**
+   * Starts installation of an array of AddonInstalls notifying the registered
+   * web install listener of blocked or started installs.
+   *
+   * @param   mimetype
+   *          The mimetype of add-ons being installed
+   * @param   source
+   *          The nsIDOMWindowInternal that started the installs
+   * @param   uri
+   *          the nsIURI that started the installs
+   * @param   installs
+   *          The array of AddonInstalls to be installed
+   */
+  installAddonsFromWebpage: function AMI_installAddonsFromWebpage(mimetype,
+                                                                  source,
+                                                                  uri,
+                                                                  installs) {
+    if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
+      WARN("No web installer available, cancelling all installs");
+      installs.forEach(function(install) {
+        install.cancel();
+      });
+      return;
+    }
+
+    try {
+      let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
+                        getService(Ci.amIWebInstallListener);
+
+      if (!this.isInstallAllowed(mimetype, uri)) {
+        if (weblistener.onWebInstallBlocked(source, uri, installs,
+                                            installs.length)) {
+          installs.forEach(function(install) {
+            install.install();
+          });
+        }
+      }
+      else if (weblistener.onWebInstallRequested(source, uri, installs,
+                                                 installs.length)) {
+        installs.forEach(function(install) {
+          install.install();
+        });
+      }
+    }
+    catch (e) {
+      // In the event that the weblistener throws during instatiation or when
+      // calling onWebInstallBlocked or onWebInstallRequested all of the
+      // installs should get cancelled.
+      WARN("Failure calling web installer: " + e);
+      installs.forEach(function(install) {
+        install.cancel();
+      });
+    }
+  },
+
+  /**
+   * Adds a new InstallListener if the listener is not already registered.
+   *
+   * @param   listener
+   *          The InstallListener to add
+   */
+  addInstallListener: function AMI_addInstallListener(listener) {
+    if (!this.installListeners.some(function(i) { return i == listener; }))
+      this.installListeners.push(listener);
+  },
+
+  /**
+   * Removes an InstallListener if the listener is registered.
+   *
+   * @param   listener
+   *          The InstallListener to remove
+   */
+  removeInstallListener: function AMI_removeInstallListener(listener) {
+    this.installListeners = this.installListeners.filter(function(i) {
+      return i != listener;
+    });
+  },
+
+  /**
+   * Asynchronously gets an add-on with a specific ID.
+   *
+   * @param   id
+   *          The ID of the add-on to retrieve
+   * @param   callback
+   *          The callback to pass the retrieved add-on to
+   * @throws  if the id or callback arguments are not specified
+   */
+  getAddon: function AMI_getAddon(id, callback) {
+    if (!id || !callback)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    new AsyncObjectCaller(this.providers, "getAddon", {
+      nextObject: function(caller, provider) {
+        callProvider(provider, "getAddon", null, id, function(addon) {
+          if (addon)
+            safeCall(callback, addon);
+          else
+            caller.callNext();
+        });
+      },
+
+      noMoreObjects: function(caller) {
+        safeCall(callback, null);
+      }
+    });
+  },
+
+  /**
+   * Asynchronously gets an array of add-ons.
+   *
+   * @param   ids
+   *          The array of IDs to retrieve
+   * @param   callback
+   *          The callback to pass an array of Addons to
+   * @throws  if the id or callback arguments are not specified
+   */
+  getAddons: function AMI_getAddons(ids, callback) {
+    if (!ids || !callback)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let addons = [];
+
+    new AsyncObjectCaller(ids, null, {
+      nextObject: function(caller, id) {
+        AddonManagerInternal.getAddon(id, function(addon) {
+          addons.push(addon);
+          caller.callNext();
+        });
+      },
+
+      noMoreObjects: function(caller) {
+        safeCall(callback, addons);
+      }
+    });
+  },
+
+  /**
+   * Asynchronously gets add-ons of specific types.
+   *
+   * @param   types
+   *          An optional array of types to retrieve. Each type is a string name
+   * @param   callback
+   *          The callback to pass an array of Addons to.
+   * @throws  if the callback argument is not specified
+   */
+  getAddonsByTypes: function AMI_getAddonsByTypes(types, callback) {
+    if (!callback)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let addons = [];
+
+    new AsyncObjectCaller(this.providers, "getAddonsByTypes", {
+      nextObject: function(caller, provider) {
+        callProvider(provider, "getAddonsByTypes", null, types,
+                     function(providerAddons) {
+          addons = addons.concat(providerAddons);
+          caller.callNext();
+        });
+      },
+
+      noMoreObjects: function(caller) {
+        safeCall(callback, addons);
+      }
+    });
+  },
+
+  /**
+   * Asynchronously gets add-ons that have operations waiting for an application
+   * restart to complete.
+   *
+   * @param   types
+   *          An optional array of types to retrieve. Each type is a string name
+   * @param   callback
+   *          The callback to pass the array of Addons to
+   * @throws  if the callback argument is not specified
+   */
+  getAddonsWithPendingOperations:
+  function AMI_getAddonsWithPendingOperations(types, callback) {
+    if (!callback)
+      throw Cr.NS_ERROR_INVALID_ARG;
+
+    let addons = [];
+
+    new AsyncObjectCaller(this.providers, "getAddonsWithPendingOperations", {
+      nextObject: function(caller, provider) {
+        callProvider(provider, "getAddonsWithPendingOperations", null, types,
+                     function(providerAddons) {
+          addons = addons.concat(providerAddons);
+          caller.callNext();
+        });
+      },
+
+      noMoreObjects: function(caller) {
+        safeCall(callback, addons);
+      }
+    });
+  },
+
+  /**
+   * Adds a new AddonListener if the listener is not already registered.
+   *
+   * @param   listener
+   *          The listener to add
+   */
+  addAddonListener: function AMI_addAddonListener(listener) {
+    if (!this.addonListeners.some(function(i) { return i == listener; }))
+      this.addonListeners.push(listener);
+  },
+
+  /**
+   * Removes an AddonListener if the listener is registered.
+   *
+   * @param   listener
+   *          The listener to remove
+   */
+  removeAddonListener: function AMI_removeAddonListener(listener) {
+    this.addonListeners = this.addonListeners.filter(function(i) {
+      return i != listener;
+    });
+  }
+};
+
+/**
+ * Should not be used outside of core Mozilla code. This is a private API for
+ * the startup and platform integration code to use. Refer to the methods on
+ * AddonManagerInternal for documentation however note that these methods are
+ * subject to change at any time.
+ */
+var AddonManagerPrivate = {
+  startup: function AMP_startup() {
+    AddonManagerInternal.startup();
+  },
+
+  registerProvider: function AMP_registerProvider(provider) {
+    AddonManagerInternal.registerProvider(provider);
+  },
+
+  shutdown: function AMP_shutdown() {
+    AddonManagerInternal.shutdown();
+  },
+
+  backgroundUpdateCheck: function AMP_backgroundUpdateCheck() {
+    AddonManagerInternal.backgroundUpdateCheck();
+  },
+
+  notifyAddonChanged: function AMP_notifyAddonChanged(id, type, pendingRestart) {
+    AddonManagerInternal.notifyAddonChanged(id, type, pendingRestart);
+  },
+
+  callInstallListeners: function AMP_callInstallListeners(method) {
+    return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal,
+                                                          arguments);
+  },
+
+  callAddonListeners: function AMP_callAddonListeners(method) {
+    AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, arguments);
+  }
+};
+
+/**
+ * This is the public API that UI and developers should be calling. All methods
+ * just forward to AddonManagerInternal.
+ */
+var AddonManager = {
+  // Constants for the AddonInstall.state property
+  // The install is available for download.
+  STATE_AVAILABLE: 0,
+  // The install is being downloaded.
+  STATE_DOWNLOADING: 1,
+  // The install is checking for compatibility information.
+  STATE_CHECKING: 2,
+  // The install is downloaded and ready to install.
+  STATE_DOWNLOADED: 3,
+  // The download failed.
+  STATE_DOWNLOAD_FAILED: 4,
+  // The add-on is being installed.
+  STATE_INSTALLING: 5,
+  // The add-on has been installed.
+  STATE_INSTALLED: 6,
+  // The install failed.
+  STATE_INSTALL_FAILED: 7,
+  // The install has been cancelled.
+  STATE_CANCELLED: 8,
+
+  // Constants representing different types of errors while downloading an
+  // add-on.
+  // The download failed due to network problems.
+  ERROR_NETWORK_FAILURE: -1,
+  // The downloaded file did not match the provided hash.
+  ERROR_INCORRECT_HASH: -2,
+  // The downloaded file seems to be corrupted in some way.
+  ERROR_CORRUPT_FILE: -3,
+
+  // Constants to indicate why an update check is being performed
+  // Update check has been requested by the user.
+  UPDATE_WHEN_USER_REQUESTED: 1,
+  // Update check is necessary to see if the Addon is compatibile with a new
+  // version of the application.
+  UPDATE_WHEN_NEW_APP_DETECTED: 2,
+  // Update check is necessary because a new application has been installed.
+  UPDATE_WHEN_NEW_APP_INSTALLED: 3,
+  // Update check is a regular background update check.
+  UPDATE_WHEN_PERIODIC_UPDATE: 16,
+  // Update check is needed to check an Addon that is being installed.
+  UPDATE_WHEN_ADDON_INSTALLED: 17,
+
+  // Constants for operations in Addon.pendingOperations
+  // Indicates that the Addon will be enabled after the application restarts.
+  PENDING_ENABLE: 1,
+  // Indicates that the Addon will be disabled after the application restarts.
+  PENDING_DISABLE: 2,
+  // Indicates that the Addon will be uninstalled after the application restarts.
+  PENDING_UNINSTALL: 4,
+  // Indicates that the Addon will be installed after the application restarts.
+  PENDING_INSTALL: 8,
+
+  // Constants for permissions in Addon.permissions.
+  // Indicates that the Addon can be uninstalled.
+  PERM_CAN_UNINSTALL: 1,
+  // Indicates that the Addon can be enabled by the user.
+  PERM_CAN_ENABLE: 2,
+  // Indicates that the Addon can be disabled by the user.
+  PERM_CAN_DISABLE: 4,
+  // Indicates that the Addon can be upgraded.
+  PERM_CAN_UPGRADE: 8,
+
+  getInstallForURL: function AM_getInstallForURL(url, callback, mimetype, hash,
+                                                 name, iconURL, version,
+                                                 loadgroup) {
+    AddonManagerInternal.getInstallForURL(url, callback, mimetype, hash, name,
+                                         iconURL, version, loadgroup);
+  },
+
+  getInstallForFile: function AM_getInstallForFile(file, callback, mimetype) {
+    AddonManagerInternal.getInstallForFile(file, callback, mimetype);
+  },
+
+  getAddon: function AM_getAdon(id, callback) {
+    AddonManagerInternal.getAddon(id, callback);
+  },
+
+  getAddons: function AM_getAddons(ids, callback) {
+    AddonManagerInternal.getAddons(ids, callback);
+  },
+
+  getAddonsWithPendingOperations:
+  function AM_getAddonsWithPendingOperations(types, callback) {
+    AddonManagerInternal.getAddonsWithPendingOperations(types, callback);
+  },
+
+  getAddonsByTypes: function AM_getAddonsByTypes(types, callback) {
+    AddonManagerInternal.getAddonsByTypes(types, callback);
+  },
+
+  getInstalls: function AM_getInstalls(types, callback) {
+    AddonManagerInternal.getInstalls(types, callback);
+  },
+
+  isInstallEnabled: function AM_isInstallEnabled(type) {
+    return AddonManagerInternal.isInstallEnabled(type);
+  },
+
+  isInstallAllowed: function AM_isInstallAllowed(type, uri) {
+    return AddonManagerInternal.isInstallAllowed(type, uri);
+  },
+
+  installAddonsFromWebpage: function AM_installAddonsFromWebpage(type, source,
+                                                                 uri, installs) {
+    AddonManagerInternal.installAddonsFromWebpage(type, source, uri, installs);
+  },
+
+  addInstallListener: function AM_addInstallListener(listener) {
+    AddonManagerInternal.addInstallListener(listener);
+  },
+
+  removeInstallListener: function AM_removeInstallListener(listener) {
+    AddonManagerInternal.removeInstallListener(listener);
+  },
+
+  addAddonListener: function AM_addAddonListener(listener) {
+    AddonManagerInternal.addAddonListener(listener);
+  },
+
+  removeAddonListener: function AM_removeAddonListener(listener) {
+    AddonManagerInternal.removeAddonListener(listener);
+  }
+};
--- a/toolkit/mozapps/extensions/Makefile.in
+++ b/toolkit/mozapps/extensions/Makefile.in
@@ -36,30 +36,56 @@
 
 DEPTH     = ../../..
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-MODULE = extensions
+MODULE         = extensions
+LIBRARY_NAME   = extensions
+IS_COMPONENT   = 1
+MODULE_NAME    = AddonsModule
+GRE_MODULE     = 1
+LIBXUL_LIBRARY = 1
+EXPORT_LIBRARY = 1
 
 XPIDLSRCS = \
+  amIInstallTrigger.idl \
+  amIWebInstallListener.idl \
+  amIWebInstaller.idl \
   nsIAddonRepository.idl \
-  nsIExtensionManager.idl \
+  $(NULL)
+
+CPPSRCS		= \
+  amInstallTrigger.cpp \
   $(NULL)
 
 EXTRA_PP_COMPONENTS = \
   nsAddonRepository.js \
   nsBlocklistService.js \
-  nsExtensionManager.js \
+  addonManager.js \
+  amContentHandler.js \
+  amWebInstallListener.js \
+  $(NULL)
+
+EXTRA_PP_JS_MODULES = \
+  AddonManager.jsm \
+  XPIProvider.jsm \
+  AddonUpdateChecker.jsm \
   $(NULL)
 
 EXTRA_JS_MODULES = \
   LightweightThemeManager.jsm \
   $(NULL)
 
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
+EXTRA_DSO_LDOPTS = \
+		$(MOZ_JS_LIBS) \
+		$(MOZ_UNICHARUTIL_LIBS) \
+		$(MOZ_COMPONENT_LIBS) \
+		$(NULL)
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -0,0 +1,191 @@
+/*
+# ***** 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
+# the Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# 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 *****
+*/
+
+/**
+ * This component serves as integration between the platform and AddonManager.
+ * It is responsible for initializing and shutting down the AddonManager as well
+ * as passing new installs from webpages to the AddonManager.
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+const PREF_EM_UPDATE_INTERVAL = "extensions.update.interval";
+
+// The old XPInstall error codes
+const EXECUTION_ERROR   = -203;
+const CANT_READ_ARCHIVE = -207;
+const USER_CANCELLED    = -210;
+const DOWNLOAD_ERROR    = -228;
+const UNSUPPORTED_TYPE  = -244;
+const SUCCESS = 0;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var gSingleton = null;
+
+function amManager() {
+  Components.utils.import("resource://gre/modules/AddonManager.jsm");
+}
+
+amManager.prototype = {
+  observe: function AMC_observe(subject, topic, data) {
+    let os = Cc["@mozilla.org/observer-service;1"].
+             getService(Ci.nsIObserverService);
+
+    switch (topic) {
+    case "profile-after-change":
+      os.addObserver(this, "xpcom-shutdown", false);
+      AddonManagerPrivate.startup();
+      break;
+    case "xpcom-shutdown":
+      os.removeObserver(this, "xpcom-shutdown");
+      AddonManagerPrivate.shutdown();
+      break;
+    }
+  },
+
+  /**
+   * @see amIWebInstaller.idl
+   */
+  isInstallEnabled: function AMC_isInstallEnabled(mimetype, referer) {
+    return AddonManager.isInstallEnabled(mimetype);
+  },
+
+  /**
+   * @see amIWebInstaller.idl
+   */
+  installAddonsFromWebpage: function AMC_installAddonsFromWebpage(mimetype,
+                                                                  window,
+                                                                  referer, uris,
+                                                                  hashes, names,
+                                                                  icons, callback) {
+    if (uris.length == 0)
+      return false;
+
+    let retval = true;
+    if (!AddonManager.isInstallAllowed(mimetype, referer)) {
+      callback = null;
+      retval = false;
+    }
+
+    let loadgroup = null;
+
+    try {
+      loadgroup = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIDocumentLoader).loadGroup;
+    }
+    catch (e) {
+    }
+
+    let installs = [];
+    function buildNextInstall() {
+      if (uris.length == 0) {
+        AddonManager.installAddonsFromWebpage(mimetype, window, referer, installs);
+        return;
+      }
+      let uri = uris.shift();
+      AddonManager.getInstallForURL(uri, function(install) {
+        if (install) {
+          installs.push(install);
+          if (callback) {
+            install.addListener({
+              onDownloadCancelled: function(install) {
+                callback.onInstallEnded(uri, USER_CANCELLED);
+              },
+
+              onDownloadFailed: function(install, error) {
+                if (error == AddonManager.ERROR_CORRUPT_FILE)
+                  callback.onInstallEnded(uri, CANT_READ_ARCHIVE);
+                else
+                  callback.onInstallEnded(uri, DOWNLOAD_ERROR);
+              },
+
+              onInstallFailed: function(install, error) {
+                callback.onInstallEnded(uri, EXECUTION_ERROR);
+              },
+
+              onInstallEnded: function(install, status) {
+                callback.onInstallEnded(uri, SUCCESS);
+              }
+            });
+          }
+        }
+        else if (callback) {
+          callback.callback(uri, UNSUPPORTED_TYPE);
+        }
+        buildNextInstall();
+      }, mimetype, hashes.shift(), names.shift(), icons.shift(), null, loadgroup);
+    }
+    buildNextInstall();
+
+    return retval;
+  },
+
+  notify: function AMC_notify(timer) {
+    AddonManagerPrivate.backgroundUpdateCheck();
+  },
+
+  classDescription: "Addons Manager",
+  contractID: "@mozilla.org/addons/integration;1",
+  classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
+  _xpcom_categories: [{ category: "profile-after-change" },
+                      { category: "update-timer",
+                        value: "@mozilla.org/addons/integration;1," +
+                               "getService,addon-background-update-timer," +
+                               PREF_EM_UPDATE_INTERVAL + ",86400" }],
+  _xpcom_factory: {
+    createInstance: function(outer, iid) {
+      if (outer != null)
+        throw Cr.NS_ERROR_NO_AGGREGATION;
+  
+      if (!gSingleton)
+        gSingleton = new amManager();
+      return gSingleton.QueryInterface(iid);
+    }
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstaller,
+                                         Ci.nsITimerCallback,
+                                         Ci.nsIObserver])
+};
+
+function NSGetModule(compMgr, fileSpec)
+  XPCOMUtils.generateModule([amManager]);
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/amIWebInstaller.idl
@@ -0,0 +1,95 @@
+/* ***** 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
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * 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 ***** */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindowInternal;
+interface nsIVariant;
+interface amIInstallCallback;
+interface nsIURI;
+
+/**
+ * This interface is used to allow webpages to start installing add-ons.
+ */
+[scriptable, uuid(4fdf4f84-73dc-4857-9bbe-84895e8afd5d)]
+interface amIWebInstaller : nsISupports
+{
+  /**
+   * Checks if installation is enabled for a webpage.
+   *
+   * @param   mimetype
+   *          The mimetype for the add-on to be installed
+   * @param   referer
+   *          The URL of the webpage trying to install an add-on
+   * @return  true if installation is enabled
+   */
+  boolean isInstallEnabled(in AString mimetype, in nsIURI referer);
+
+  /**
+   * Installs an array of add-ons at the request of a webpage
+   *
+   * @param   mimetype
+   *          The mimetype for the add-ons
+   * @param   window
+   *          The window installing the add-ons
+   * @param   referer
+   *          The URI for the webpage installing the add-ons
+   * @param   uris
+   *          The URIs of add-ons to be installed
+   * @param   hashes
+   *          The hashes for the add-ons to be installed
+   * @param   names
+   *          The names for the add-ons to be installed
+   * @param   icons
+   *          The icons for the add-ons to be installed
+   * @param   callback
+   *          An optional callback to notify about installation success and
+   *          failure
+   * @param   installCount
+   *          An optional argument including the number of add-ons to install
+   * @return  true if the installation was successfully started
+   */
+  boolean installAddonsFromWebpage(in AString mimetype,
+                                   in nsIDOMWindowInternal window,
+                                   in nsIURI referer,
+                                   [array, size_is(installCount)] in wstring uris,
+                                   [array, size_is(installCount)] in wstring hashes,
+                                   [array, size_is(installCount)] in wstring names,
+                                   [array, size_is(installCount)] in wstring icons,
+                                   [optional] in amIInstallCallback callback,
+                                   [optional] in PRUint32 installCount);
+};