From 2811cdd62049b78ea67e44c3222fcd6247c2e7e8 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Thu, 29 Apr 2010 13:11:23 -0700 Subject: [PATCH] Bug 553169: Implement the basic extension manager API and platform integration. r=robstrong --- browser/installer/package-manifest.in | 2 +- toolkit/mozapps/extensions/AddonManager.jsm | 877 ++++++++++++++++++ toolkit/mozapps/extensions/Makefile.in | 32 +- toolkit/mozapps/extensions/addonManager.js | 191 ++++ .../mozapps/extensions/amIWebInstaller.idl | 95 ++ 5 files changed, 1193 insertions(+), 4 deletions(-) create mode 100644 toolkit/mozapps/extensions/AddonManager.jsm create mode 100644 toolkit/mozapps/extensions/addonManager.js create mode 100644 toolkit/mozapps/extensions/amIWebInstaller.idl diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index ec174e3a1f5e..70046da14fc1 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -295,7 +295,7 @@ @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 diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm new file mode 100644 index 000000000000..7d33b2a7b81b --- /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 +# +# 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); + } +}; diff --git a/toolkit/mozapps/extensions/Makefile.in b/toolkit/mozapps/extensions/Makefile.in index e935b31d2d27..09c8858f6320 100644 --- a/toolkit/mozapps/extensions/Makefile.in +++ b/toolkit/mozapps/extensions/Makefile.in @@ -41,17 +41,37 @@ 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 = \ @@ -62,4 +82,10 @@ ifdef ENABLE_TESTS DIRS += test endif +EXTRA_DSO_LDOPTS = \ + $(MOZ_JS_LIBS) \ + $(MOZ_UNICHARUTIL_LIBS) \ + $(MOZ_COMPONENT_LIBS) \ + $(NULL) + include $(topsrcdir)/config/rules.mk diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js new file mode 100644 index 000000000000..4ce9b45c84aa --- /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 +# +# 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]); diff --git a/toolkit/mozapps/extensions/amIWebInstaller.idl b/toolkit/mozapps/extensions/amIWebInstaller.idl new file mode 100644 index 000000000000..f18a009219df --- /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 + * + * 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); +};