summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/AddonManager.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/AddonManager.jsm')
-rw-r--r--toolkit/mozapps/extensions/AddonManager.jsm3026
1 files changed, 3026 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm
new file mode 100644
index 000000000..51030cb56
--- /dev/null
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -0,0 +1,3026 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as
+// most tests later register different nsIAppInfo implementations, which
+// wouldn't be reflected in Services.appinfo anymore, as the lazy getter
+// underlying it would have been initialized if we used it here.
+if ("@mozilla.org/xre/app-info;1" in Cc) {
+ let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
+ if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ // Refuse to run in child processes.
+ throw new Error("You cannot use the AddonManager in child processes!");
+ }
+}
+
+
+const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
+const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled";
+const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
+const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
+const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion";
+const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
+const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility";
+const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url";
+const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
+const PREF_APP_UPDATE_AUTO = "app.update.auto";
+const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
+const PREF_SELECTED_LOCALE = "general.useragent.locale";
+const UNKNOWN_XPCOM_ABI = "unknownABI";
+
+const UPDATE_REQUEST_VERSION = 2;
+const CATEGORY_UPDATE_PARAMS = "extension-update-params";
+
+const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
+
+const KEY_PROFILEDIR = "ProfD";
+const KEY_APPDIR = "XCurProcD";
+const FILE_BLOCKLIST = "blocklist.xml";
+
+const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
+const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
+var PREF_EM_CHECK_COMPATIBILITY;
+
+const TOOLKIT_ID = "toolkit@mozilla.org";
+
+const VALID_TYPES_REGEXP = /^[\w\-]+$/;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
+ "resource://gre/modules/addons/AddonRepository.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
+ let certUtils = {};
+ Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
+ return certUtils;
+});
+
+
+this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
+
+const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
+
+// A list of providers to load by default
+const DEFAULT_PROVIDERS = [
+ "resource://gre/modules/addons/XPIProvider.jsm",
+ "resource://gre/modules/LightweightThemeManager.jsm"
+];
+
+Cu.import("resource://gre/modules/Log.jsm");
+// Configure a logger at the parent 'addons' level to format
+// messages for all the modules under addons.*
+const PARENT_LOGGER_ID = "addons";
+let parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
+parentLogger.level = Log.Level.Warn;
+let formatter = new Log.BasicFormatter();
+// Set parent logger (and its children) to append to
+// the Javascript section of the Browser Console
+parentLogger.addAppender(new Log.ConsoleAppender(formatter));
+// Set parent logger (and its children) to
+// also append to standard out
+parentLogger.addAppender(new Log.DumpAppender(formatter));
+
+// Create a new logger (child of 'addons' logger)
+// for use by the Addons Manager
+const LOGGER_ID = "addons.manager";
+let logger = Log.repository.getLogger(LOGGER_ID);
+
+// Provide the ability to enable/disable logging
+// messages at runtime.
+// If the "extensions.logging.enabled" preference is
+// missing or 'false', messages at the WARNING and higher
+// severity should be logged to the JS console and standard error.
+// If "extensions.logging.enabled" is set to 'true', messages
+// at DEBUG and higher should go to JS console and standard error.
+const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+
+const UNNAMED_PROVIDER = "<unnamed-provider>";
+function providerName(aProvider) {
+ return aProvider.name || UNNAMED_PROVIDER;
+}
+
+/**
+ * Preference listener which listens for a change in the
+ * "extensions.logging.enabled" preference and changes the logging level of the
+ * parent 'addons' level logger accordingly.
+ */
+var PrefObserver = {
+ init: function PrefObserver_init() {
+ Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
+ Services.obs.addObserver(this, "xpcom-shutdown", false);
+ this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
+ },
+
+ observe: function PrefObserver_observe(aSubject, aTopic, aData) {
+ if (aTopic == "xpcom-shutdown") {
+ Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ }
+ else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
+ let debugLogEnabled = false;
+ try {
+ debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED);
+ }
+ catch (e) {
+ }
+ if (debugLogEnabled) {
+ parentLogger.level = Log.Level.Debug;
+ }
+ else {
+ parentLogger.level = Log.Level.Warn;
+ }
+ }
+ }
+};
+
+PrefObserver.init();
+
+/**
+ * Calls a callback method consuming any thrown exception. Any parameters after
+ * the callback parameter will be passed to the callback.
+ *
+ * @param aCallback
+ * The callback method to call
+ */
+function safeCall(aCallback, ...aArgs) {
+ try {
+ aCallback.apply(null, aArgs);
+ }
+ catch (e) {
+ logger.warn("Exception calling callback", e);
+ }
+}
+
+/**
+ * Report an exception thrown by a provider API method.
+ */
+function reportProviderError(aProvider, aMethod, aError) {
+ let method = `provider ${providerName(aProvider)}.${aMethod}`;
+ AddonManagerPrivate.recordException("AMI", method, aError);
+ logger.error("Exception calling " + method, aError);
+}
+
+/**
+ * Calls a method on a provider if it exists and consumes any thrown exception.
+ * Any parameters after the aDefault parameter are passed to the provider's method.
+ *
+ * @param aProvider
+ * The provider to call
+ * @param aMethod
+ * The method name to call
+ * @param aDefault
+ * 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 aDefault if the provider does not
+ * implement method or throws an error
+ */
+function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
+ if (!(aMethod in aProvider))
+ return aDefault;
+
+ try {
+ return aProvider[aMethod].apply(aProvider, aArgs);
+ }
+ catch (e) {
+ reportProviderError(aProvider, aMethod, e);
+ return aDefault;
+ }
+}
+
+/**
+ * Calls a method on a provider if it exists and consumes any thrown exception.
+ * Parameters after aMethod are passed to aProvider.aMethod().
+ * The last parameter must be a callback function.
+ * If the provider does not implement the method, or the method throws, calls
+ * the callback with 'undefined'.
+ *
+ * @param aProvider
+ * The provider to call
+ * @param aMethod
+ * The method name to call
+ */
+function callProviderAsync(aProvider, aMethod, ...aArgs) {
+ let callback = aArgs[aArgs.length - 1];
+ if (!(aMethod in aProvider)) {
+ callback(undefined);
+ return;
+ }
+ try {
+ return aProvider[aMethod].apply(aProvider, aArgs);
+ }
+ catch (e) {
+ reportProviderError(aProvider, aMethod, e);
+ callback(undefined);
+ return;
+ }
+}
+
+/**
+ * Gets the currently selected locale for display.
+ * @return the selected locale or "en-US" if none is selected
+ */
+function getLocale() {
+ try {
+ if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE))
+ return Services.locale.getLocaleComponentForUserAgent();
+ }
+ catch (e) { }
+
+ try {
+ let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
+ Ci.nsIPrefLocalizedString);
+ if (locale)
+ return locale;
+ }
+ catch (e) { }
+
+ try {
+ return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
+ }
+ catch (e) { }
+
+ return "en-US";
+}
+
+/**
+ * Previously the APIs for installing add-ons from webpages accepted nsIURI
+ * arguments for the installing page. They now take an nsIPrincipal but for now
+ * maintain backwards compatibility by converting an nsIURI to an nsIPrincipal.
+ *
+ * @param aPrincipalOrURI
+ * The argument passed to the API function. Can be null, an nsIURI or
+ * an nsIPrincipal.
+ * @return an nsIPrincipal.
+ */
+function ensurePrincipal(principalOrURI) {
+ if (principalOrURI instanceof Ci.nsIPrincipal)
+ return principalOrURI;
+
+ logger.warn("Deprecated API call, please pass a non-null nsIPrincipal instead of an nsIURI");
+
+ // Previously a null installing URI meant allowing the install regardless.
+ if (!principalOrURI) {
+ return Services.scriptSecurityManager.getSystemPrincipal();
+ }
+
+ if (principalOrURI instanceof Ci.nsIURI) {
+ return Services.scriptSecurityManager.createCodebasePrincipal(principalOrURI, {
+ inBrowser: true
+ });
+ }
+
+ // Just return whatever we have, the API method will log an error about it.
+ return principalOrURI;
+}
+
+/**
+ * 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 aObjects
+ * The array of objects to iterate through
+ * @param aMethod
+ * An optional method name, if not null any objects without this method
+ * will not be passed to the listener
+ * @param aListener
+ * 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(aObjects, aMethod, aListener) {
+ this.objects = [...aObjects];
+ this.method = aMethod;
+ this.listener = aListener;
+
+ 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();
+ }
+};
+
+/**
+ * Listens for a browser changing origin and cancels the installs that were
+ * started by it.
+ */
+function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) {
+ this.browser = aBrowser;
+ this.principal = aInstallingPrincipal;
+ this.installs = aInstalls;
+ this.installCount = aInstalls.length;
+
+ aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+ Services.obs.addObserver(this, "message-manager-disconnect", true);
+
+ for (let install of this.installs)
+ install.addListener(this);
+
+ this.registered = true;
+}
+
+BrowserListener.prototype = {
+ browser: null,
+ installs: null,
+ installCount: null,
+ registered: false,
+
+ unregister: function() {
+ if (!this.registered)
+ return;
+ this.registered = false;
+
+ Services.obs.removeObserver(this, "message-manager-disconnect");
+ // The browser may have already been detached
+ if (this.browser.removeProgressListener)
+ this.browser.removeProgressListener(this);
+
+ for (let install of this.installs)
+ install.removeListener(this);
+ this.installs = null;
+ },
+
+ cancelInstalls: function() {
+ for (let install of this.installs) {
+ try {
+ install.cancel();
+ }
+ catch (e) {
+ // Some installs may have already failed or been cancelled, ignore these
+ }
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ if (subject != this.browser.messageManager)
+ return;
+
+ // The browser's message manager has closed and so the browser is
+ // going away, cancel all installs
+ this.cancelInstalls();
+ },
+
+ onLocationChange: function(webProgress, request, location) {
+ if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal))
+ return;
+
+ // The browser has navigated to a new origin so cancel all installs
+ this.cancelInstalls();
+ },
+
+ onDownloadCancelled: function(install) {
+ // Don't need to hear more events from this install
+ install.removeListener(this);
+
+ // Once all installs have ended unregister everything
+ if (--this.installCount == 0)
+ this.unregister();
+ },
+
+ onDownloadFailed: function(install) {
+ this.onDownloadCancelled(install);
+ },
+
+ onInstallFailed: function(install) {
+ this.onDownloadCancelled(install);
+ },
+
+ onInstallEnded: function(install) {
+ this.onDownloadCancelled(install);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+ Ci.nsIWebProgressListener,
+ Ci.nsIObserver])
+};
+
+/**
+ * This represents an author of an add-on (e.g. creator or developer)
+ *
+ * @param aName
+ * The name of the author
+ * @param aURL
+ * The URL of the author's profile page
+ */
+function AddonAuthor(aName, aURL) {
+ this.name = aName;
+ this.url = aURL;
+}
+
+AddonAuthor.prototype = {
+ name: null,
+ url: null,
+
+ // Returns the author's name, defaulting to the empty string
+ toString: function AddonAuthor_toString() {
+ return this.name || "";
+ }
+}
+
+/**
+ * This represents an screenshot for an add-on
+ *
+ * @param aURL
+ * The URL to the full version of the screenshot
+ * @param aWidth
+ * The width in pixels of the screenshot
+ * @param aHeight
+ * The height in pixels of the screenshot
+ * @param aThumbnailURL
+ * The URL to the thumbnail version of the screenshot
+ * @param aThumbnailWidth
+ * The width in pixels of the thumbnail version of the screenshot
+ * @param aThumbnailHeight
+ * The height in pixels of the thumbnail version of the screenshot
+ * @param aCaption
+ * The caption of the screenshot
+ */
+function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL,
+ aThumbnailWidth, aThumbnailHeight, aCaption) {
+ this.url = aURL;
+ if (aWidth) this.width = aWidth;
+ if (aHeight) this.height = aHeight;
+ if (aThumbnailURL) this.thumbnailURL = aThumbnailURL;
+ if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth;
+ if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight;
+ if (aCaption) this.caption = aCaption;
+}
+
+AddonScreenshot.prototype = {
+ url: null,
+ width: null,
+ height: null,
+ thumbnailURL: null,
+ thumbnailWidth: null,
+ thumbnailHeight: null,
+ caption: null,
+
+ // Returns the screenshot URL, defaulting to the empty string
+ toString: function AddonScreenshot_toString() {
+ return this.url || "";
+ }
+}
+
+
+/**
+ * This represents a compatibility override for an addon.
+ *
+ * @param aType
+ * Overrride type - "compatible" or "incompatible"
+ * @param aMinVersion
+ * Minimum version of the addon to match
+ * @param aMaxVersion
+ * Maximum version of the addon to match
+ * @param aAppID
+ * Application ID used to match appMinVersion and appMaxVersion
+ * @param aAppMinVersion
+ * Minimum version of the application to match
+ * @param aAppMaxVersion
+ * Maximum version of the application to match
+ */
+function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID,
+ aAppMinVersion, aAppMaxVersion) {
+ this.type = aType;
+ this.minVersion = aMinVersion;
+ this.maxVersion = aMaxVersion;
+ this.appID = aAppID;
+ this.appMinVersion = aAppMinVersion;
+ this.appMaxVersion = aAppMaxVersion;
+}
+
+AddonCompatibilityOverride.prototype = {
+ /**
+ * Type of override - "incompatible" or "compatible".
+ * Only "incompatible" is supported for now.
+ */
+ type: null,
+
+ /**
+ * Min version of the addon to match.
+ */
+ minVersion: null,
+
+ /**
+ * Max version of the addon to match.
+ */
+ maxVersion: null,
+
+ /**
+ * Application ID to match.
+ */
+ appID: null,
+
+ /**
+ * Min version of the application to match.
+ */
+ appMinVersion: null,
+
+ /**
+ * Max version of the application to match.
+ */
+ appMaxVersion: null
+};
+
+
+/**
+ * A type of add-on, used by the UI to determine how to display different types
+ * of add-ons.
+ *
+ * @param aID
+ * The add-on type ID
+ * @param aLocaleURI
+ * The URI of a localized properties file to get the displayable name
+ * for the type from
+ * @param aLocaleKey
+ * The key for the string in the properties file or the actual display
+ * name if aLocaleURI is null. Include %ID% to include the type ID in
+ * the key
+ * @param aViewType
+ * The optional type of view to use in the UI
+ * @param aUIPriority
+ * The priority is used by the UI to list the types in order. Lower
+ * values push the type higher in the list.
+ * @param aFlags
+ * An option set of flags that customize the display of the add-on in
+ * the UI.
+ */
+function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
+ if (!aID)
+ throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG);
+
+ if (aViewType && aUIPriority === undefined)
+ throw Components.Exception("An AddonType with a defined view must have a set UI priority",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aLocaleKey)
+ throw Components.Exception("An AddonType must have a displayable name",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.id = aID;
+ this.uiPriority = aUIPriority;
+ this.viewType = aViewType;
+ this.flags = aFlags;
+
+ if (aLocaleURI) {
+ this.__defineGetter__("name", function nameGetter() {
+ delete this.name;
+ let bundle = Services.strings.createBundle(aLocaleURI);
+ this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID));
+ return this.name;
+ });
+ }
+ else {
+ this.name = aLocaleKey;
+ }
+}
+
+var gStarted = false;
+var gStartupComplete = false;
+var gCheckCompatibility = true;
+var gStrictCompatibility = true;
+var gCheckUpdateSecurityDefault = true;
+var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
+var gUpdateEnabled = true;
+var gAutoUpdateDefault = true;
+var gShutdownBarrier = null;
+var gRepoShutdownState = "";
+var gShutdownInProgress = false;
+
+/**
+ * This is the real manager, kept here rather than in AddonManager to keep its
+ * contents hidden from API users.
+ */
+var AddonManagerInternal = {
+ managerListeners: [],
+ installListeners: [],
+ addonListeners: [],
+ typeListeners: [],
+ pendingProviders: new Set(),
+ providers: new Set(),
+ providerShutdowns: new Map(),
+ types: {},
+ startupChanges: {},
+ // Store telemetry details per addon provider
+ telemetryDetails: {},
+
+ recordTimestamp: function AMI_recordTimestamp(name, value) {
+ this.TelemetryTimestamps.add(name, value);
+ },
+
+ validateBlocklist: function AMI_validateBlocklist() {
+ let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
+
+ // If there is no application shipped blocklist then there is nothing to do
+ if (!appBlocklist.exists())
+ return;
+
+ let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+
+ // If there is no blocklist in the profile then copy the application shipped
+ // one there
+ if (!profileBlocklist.exists()) {
+ try {
+ appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
+ }
+ catch (e) {
+ logger.warn("Failed to copy the application shipped blocklist to the profile", e);
+ }
+ return;
+ }
+
+ let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
+ createInstance(Ci.nsIFileInputStream);
+ try {
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+ createInstance(Ci.nsIConverterInputStream);
+ fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
+ cstream.init(fileStream, "UTF-8", 0, 0);
+
+ let data = "";
+ let str = {};
+ let read = 0;
+ do {
+ read = cstream.readString(0xffffffff, str);
+ data += str.value;
+ } while (read != 0);
+
+ let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doc = parser.parseFromString(data, "text/xml");
+ }
+ catch (e) {
+ logger.warn("Application shipped blocklist could not be loaded", e);
+ return;
+ }
+ finally {
+ try {
+ fileStream.close();
+ }
+ catch (e) {
+ logger.warn("Unable to close blocklist file stream", e);
+ }
+ }
+
+ // If the namespace is incorrect then ignore the application shipped
+ // blocklist
+ if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
+ logger.warn("Application shipped blocklist has an unexpected namespace (" +
+ doc.documentElement.namespaceURI + ")");
+ return;
+ }
+
+ // If there is no lastupdate information then ignore the application shipped
+ // blocklist
+ if (!doc.documentElement.hasAttribute("lastupdate"))
+ return;
+
+ // If the application shipped blocklist is older than the profile blocklist
+ // then do nothing
+ if (doc.documentElement.getAttribute("lastupdate") <=
+ profileBlocklist.lastModifiedTime)
+ return;
+
+ // Otherwise copy the application shipped blocklist to the profile
+ try {
+ appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
+ }
+ catch (e) {
+ logger.warn("Failed to copy the application shipped blocklist to the profile", e);
+ }
+ },
+
+ /**
+ * Start up a provider, and register its shutdown hook if it has one
+ */
+ _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ logger.debug(`Starting provider: ${providerName(aProvider)}`);
+ callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
+ if ('shutdown' in aProvider) {
+ let name = providerName(aProvider);
+ let AMProviderShutdown = () => {
+ // If the provider has been unregistered, it will have been removed from
+ // this.providers. If it hasn't been unregistered, then this is a normal
+ // shutdown - and we move it to this.pendingProviders incase we're
+ // running in a test that will start AddonManager again.
+ if (this.providers.has(aProvider)) {
+ this.providers.delete(aProvider);
+ this.pendingProviders.add(aProvider);
+ }
+
+ return new Promise((resolve, reject) => {
+ logger.debug("Calling shutdown blocker for " + name);
+ resolve(aProvider.shutdown());
+ })
+ .catch(err => {
+ logger.warn("Failure during shutdown of " + name, err);
+ AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err);
+ });
+ };
+ logger.debug("Registering shutdown blocker for " + name);
+ this.providerShutdowns.set(aProvider, AMProviderShutdown);
+ AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
+ }
+
+ this.pendingProviders.delete(aProvider);
+ this.providers.add(aProvider);
+ logger.debug(`Provider finished startup: ${providerName(aProvider)}`);
+ },
+
+ /**
+ * Initializes the AddonManager, loading any known providers and initializing
+ * them.
+ */
+ startup: function AMI_startup() {
+ try {
+ if (gStarted)
+ return;
+
+ this.recordTimestamp("AMI_startup_begin");
+
+ // clear this for xpcshell test restarts
+ for (let provider in this.telemetryDetails)
+ delete this.telemetryDetails[provider];
+
+ let appChanged = undefined;
+
+ let oldAppVersion = null;
+ try {
+ oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
+ appChanged = Services.appinfo.version != oldAppVersion;
+ }
+ catch (e) { }
+
+ let oldPlatformVersion = null;
+ try {
+ oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION);
+ }
+ catch (e) { }
+
+ if (appChanged !== false) {
+ logger.debug("Application has been upgraded");
+ Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
+ Services.appinfo.version);
+ Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
+ Services.appinfo.platformVersion);
+ Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
+ (appChanged === undefined ? 0 : -1));
+ this.validateBlocklist();
+ }
+
+ PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
+ Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
+
+ try {
+ gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY);
+ } catch (e) {}
+ Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false);
+
+ try {
+ gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY);
+ } catch (e) {}
+ Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false);
+
+ try {
+ let defaultBranch = Services.prefs.getDefaultBranch("");
+ gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
+ } catch(e) {}
+
+ try {
+ gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
+ } catch (e) {}
+ Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false);
+
+ try {
+ gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED);
+ } catch (e) {}
+ Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false);
+
+ try {
+ gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
+ } catch (e) {}
+ Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false);
+
+ let defaultProvidersEnabled = true;
+ try {
+ defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED);
+ } catch (e) {}
+ AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled);
+
+ // Ensure all default providers have had a chance to register themselves
+ if (defaultProvidersEnabled) {
+ for (let url of DEFAULT_PROVIDERS) {
+ try {
+ let scope = {};
+ Components.utils.import(url, scope);
+ // Sanity check - make sure the provider exports a symbol that
+ // has a 'startup' method
+ let syms = Object.keys(scope);
+ if ((syms.length < 1) ||
+ (typeof scope[syms[0]].startup != "function")) {
+ logger.warn("Provider " + url + " has no startup()");
+ AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()");
+ }
+ logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource());
+ }
+ catch (e) {
+ AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
+ logger.error("Exception loading default provider \"" + url + "\"", e);
+ }
+ };
+ }
+
+ // Load any providers registered in the category manager
+ let catman = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
+ let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
+
+ try {
+ Components.utils.import(url, {});
+ logger.debug(`Loaded provider scope for ${url}`);
+ }
+ catch (e) {
+ AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
+ logger.error("Exception loading provider " + entry + " from category \"" +
+ url + "\"", e);
+ }
+ }
+
+ // Register our shutdown handler with the AsyncShutdown manager
+ gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down.");
+ AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.",
+ this.shutdownManager.bind(this),
+ {fetchState: this.shutdownState.bind(this)});
+
+ // Once we start calling providers we must allow all normal methods to work.
+ gStarted = true;
+
+ for (let provider of this.pendingProviders) {
+ this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion);
+ }
+
+ // If this is a new profile just pretend that there were no changes
+ if (appChanged === undefined) {
+ for (let type in this.startupChanges)
+ delete this.startupChanges[type];
+ }
+
+ gStartupComplete = true;
+ this.recordTimestamp("AMI_startup_end");
+ }
+ catch (e) {
+ logger.error("startup failed", e);
+ AddonManagerPrivate.recordException("AMI", "startup failed", e);
+ }
+
+ logger.debug("Completed startup sequence");
+ this.callManagerListeners("onStartup");
+ },
+
+ /**
+ * Registers a new AddonProvider.
+ *
+ * @param aProvider
+ * The provider to register
+ * @param aTypes
+ * An optional array of add-on types
+ */
+ registerProvider: function AMI_registerProvider(aProvider, aTypes) {
+ if (!aProvider || typeof aProvider != "object")
+ throw Components.Exception("aProvider must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.pendingProviders.add(aProvider);
+
+ if (aTypes) {
+ aTypes.forEach(function(aType) {
+ if (!(aType.id in this.types)) {
+ if (!VALID_TYPES_REGEXP.test(aType.id)) {
+ logger.warn("Ignoring invalid type " + aType.id);
+ return;
+ }
+
+ this.types[aType.id] = {
+ type: aType,
+ providers: [aProvider]
+ };
+
+ let typeListeners = this.typeListeners.slice(0);
+ for (let listener of typeListeners) {
+ safeCall(function listenerSafeCall() {
+ listener.onTypeAdded(aType);
+ });
+ }
+ }
+ else {
+ this.types[aType.id].providers.push(aProvider);
+ }
+ }, this);
+ }
+
+ // If we're registering after startup call this provider's startup.
+ if (gStarted) {
+ this._startProvider(aProvider);
+ }
+ },
+
+ /**
+ * Unregisters an AddonProvider.
+ *
+ * @param aProvider
+ * The provider to unregister
+ * @return Whatever the provider's 'shutdown' method returns (if anything).
+ * For providers that have async shutdown methods returning Promises,
+ * the caller should wait for that Promise to resolve.
+ */
+ unregisterProvider: function AMI_unregisterProvider(aProvider) {
+ if (!aProvider || typeof aProvider != "object")
+ throw Components.Exception("aProvider must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.providers.delete(aProvider);
+ // The test harness will unregister XPIProvider *after* shutdown, which is
+ // after the provider will have been moved from providers to
+ // pendingProviders.
+ this.pendingProviders.delete(aProvider);
+
+ for (let type in this.types) {
+ this.types[type].providers = this.types[type].providers.filter(function filterProvider(p) p != aProvider);
+ if (this.types[type].providers.length == 0) {
+ let oldType = this.types[type].type;
+ delete this.types[type];
+
+ let typeListeners = this.typeListeners.slice(0);
+ for (let listener of typeListeners) {
+ safeCall(function listenerSafeCall() {
+ listener.onTypeRemoved(oldType);
+ });
+ }
+ }
+ }
+
+ // If we're unregistering after startup but before shutting down,
+ // remove the blocker for this provider's shutdown and call it.
+ // If we're already shutting down, just let gShutdownBarrier call it to avoid races.
+ if (gStarted && !gShutdownInProgress) {
+ logger.debug("Unregistering shutdown blocker for " + providerName(aProvider));
+ let shutter = this.providerShutdowns.get(aProvider);
+ if (shutter) {
+ this.providerShutdowns.delete(aProvider);
+ gShutdownBarrier.client.removeBlocker(shutter);
+ return shutter();
+ }
+ }
+ return undefined;
+ },
+
+ /**
+ * Mark a provider as safe to access via AddonManager APIs, before its
+ * startup has completed.
+ *
+ * Normally a provider isn't marked as safe until after its (synchronous)
+ * startup() method has returned. Until a provider has been marked safe,
+ * it won't be used by any of the AddonManager APIs. markProviderSafe()
+ * allows a provider to mark itself as safe during its startup; this can be
+ * useful if the provider wants to perform tasks that block startup, which
+ * happen after its required initialization tasks and therefore when the
+ * provider is in a safe state.
+ *
+ * @param aProvider Provider object to mark safe
+ */
+ markProviderSafe: function AMI_markProviderSafe(aProvider) {
+ if (!gStarted) {
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+ }
+
+ if (!aProvider || typeof aProvider != "object") {
+ throw Components.Exception("aProvider must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ if (!this.pendingProviders.has(aProvider)) {
+ return;
+ }
+
+ this.pendingProviders.delete(aProvider);
+ this.providers.add(aProvider);
+ },
+
+ /**
+ * Calls a method on all registered providers if it exists and consumes any
+ * thrown exception. Return values are ignored. Any parameters after the
+ * method parameter are passed to the provider's method.
+ * WARNING: Do not use for asynchronous calls; callProviders() does not
+ * invoke callbacks if provider methods throw synchronous exceptions.
+ *
+ * @param aMethod
+ * The method name to call
+ * @see callProvider
+ */
+ callProviders: function AMI_callProviders(aMethod, ...aArgs) {
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ try {
+ if (aMethod in provider)
+ provider[aMethod].apply(provider, aArgs);
+ }
+ catch (e) {
+ reportProviderError(aProvider, aMethod, e);
+ }
+ }
+ },
+
+ /**
+ * Report the current state of asynchronous shutdown
+ */
+ shutdownState() {
+ let state = [];
+ if (gShutdownBarrier) {
+ state.push({
+ name: gShutdownBarrier.client.name,
+ state: gShutdownBarrier.state
+ });
+ }
+ state.push({
+ name: "AddonRepository: async shutdown",
+ state: gRepoShutdownState
+ });
+ return state;
+ },
+
+ /**
+ * Shuts down the addon manager and all registered providers, this must clean
+ * up everything in order for automated tests to fake restarts.
+ * @return Promise{null} that resolves when all providers and dependent modules
+ * have finished shutting down
+ */
+ shutdownManager: Task.async(function* () {
+ logger.debug("shutdown");
+ this.callManagerListeners("onShutdown");
+
+ gRepoShutdownState = "pending";
+ gShutdownInProgress = true;
+ // Clean up listeners
+ Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
+ Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
+ Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
+ Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
+ Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
+
+ let savedError = null;
+ // Only shut down providers if they've been started.
+ if (gStarted) {
+ try {
+ yield gShutdownBarrier.wait();
+ }
+ catch(err) {
+ savedError = err;
+ logger.error("Failure during wait for shutdown barrier", err);
+ AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err);
+ }
+ }
+
+ // Shut down AddonRepository after providers (if any).
+ try {
+ gRepoShutdownState = "in progress";
+ yield AddonRepository.shutdown();
+ gRepoShutdownState = "done";
+ }
+ catch(err) {
+ savedError = err;
+ logger.error("Failure during AddonRepository shutdown", err);
+ AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
+ }
+
+ logger.debug("Async provider shutdown done");
+ this.managerListeners.splice(0, this.managerListeners.length);
+ this.installListeners.splice(0, this.installListeners.length);
+ this.addonListeners.splice(0, this.addonListeners.length);
+ this.typeListeners.splice(0, this.typeListeners.length);
+ this.providerShutdowns.clear();
+ for (let type in this.startupChanges)
+ delete this.startupChanges[type];
+ gStarted = false;
+ gStartupComplete = false;
+ gShutdownBarrier = null;
+ gShutdownInProgress = false;
+ if (savedError) {
+ throw savedError;
+ }
+ }),
+
+ /**
+ * Notified when a preference we're interested in has changed.
+ *
+ * @see nsIObserver
+ */
+ observe: function AMI_observe(aSubject, aTopic, aData) {
+ switch (aData) {
+ case PREF_EM_CHECK_COMPATIBILITY: {
+ let oldValue = gCheckCompatibility;
+ try {
+ gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY);
+ } catch(e) {
+ gCheckCompatibility = true;
+ }
+
+ this.callManagerListeners("onCompatibilityModeChanged");
+
+ if (gCheckCompatibility != oldValue)
+ this.updateAddonAppDisabledStates();
+
+ break;
+ }
+ case PREF_EM_STRICT_COMPATIBILITY: {
+ let oldValue = gStrictCompatibility;
+ try {
+ gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY);
+ } catch(e) {
+ gStrictCompatibility = true;
+ }
+
+ this.callManagerListeners("onCompatibilityModeChanged");
+
+ if (gStrictCompatibility != oldValue)
+ this.updateAddonAppDisabledStates();
+
+ break;
+ }
+ case PREF_EM_CHECK_UPDATE_SECURITY: {
+ let oldValue = gCheckUpdateSecurity;
+ try {
+ gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
+ } catch(e) {
+ gCheckUpdateSecurity = true;
+ }
+
+ this.callManagerListeners("onCheckUpdateSecurityChanged");
+
+ if (gCheckUpdateSecurity != oldValue)
+ this.updateAddonAppDisabledStates();
+
+ break;
+ }
+ case PREF_EM_UPDATE_ENABLED: {
+ let oldValue = gUpdateEnabled;
+ try {
+ gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED);
+ } catch(e) {
+ gUpdateEnabled = true;
+ }
+
+ this.callManagerListeners("onUpdateModeChanged");
+ break;
+ }
+ case PREF_EM_AUTOUPDATE_DEFAULT: {
+ let oldValue = gAutoUpdateDefault;
+ try {
+ gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT);
+ } catch(e) {
+ gAutoUpdateDefault = true;
+ }
+
+ this.callManagerListeners("onUpdateModeChanged");
+ break;
+ }
+ }
+ },
+
+ /**
+ * Replaces %...% strings in an addon url (update and updateInfo) with
+ * appropriate values.
+ *
+ * @param aAddon
+ * The Addon representing the add-on
+ * @param aUri
+ * The string representation of the URI to escape
+ * @param aAppVersion
+ * The optional application version to use for %APP_VERSION%
+ * @return The appropriately escaped URI.
+ */
+ escapeAddonURI: function AMI_escapeAddonURI(aAddon, aUri, aAppVersion)
+ {
+ if (!aAddon || typeof aAddon != "object")
+ throw Components.Exception("aAddon must be an Addon object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aUri || typeof aUri != "string")
+ throw Components.Exception("aUri must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aAppVersion && typeof aAppVersion != "string")
+ throw Components.Exception("aAppVersion must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled"
+ : "userEnabled";
+
+ if (!aAddon.isCompatible)
+ addonStatus += ",incompatible";
+ if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+ addonStatus += ",blocklisted";
+ if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+ addonStatus += ",softblocked";
+
+ try {
+ var xpcomABI = Services.appinfo.XPCOMABI;
+ } catch (ex) {
+ xpcomABI = UNKNOWN_XPCOM_ABI;
+ }
+
+ let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id);
+ uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version);
+ uri = uri.replace(/%ITEM_STATUS%/g, addonStatus);
+ uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID);
+ uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion :
+ Services.appinfo.version);
+ uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION);
+ uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS);
+ uri = uri.replace(/%APP_ABI%/g, xpcomABI);
+ uri = uri.replace(/%APP_LOCALE%/g, getLocale());
+ uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version);
+
+ // Replace custom parameters (names of custom parameters must have at
+ // least 3 characters to prevent lookups for something like %D0%C8)
+ var catMan = null;
+ uri = uri.replace(/%(\w{3,})%/g, function parameterReplace(aMatch, aParam) {
+ if (!catMan) {
+ catMan = Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager);
+ }
+
+ try {
+ var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam);
+ var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2);
+ return paramHandler.getPropertyAsAString(aParam);
+ }
+ catch(e) {
+ return aMatch;
+ }
+ });
+
+ // escape() does not properly encode + symbols in any embedded FVF strings.
+ return uri.replace(/\+/g, "%2B");
+ },
+
+ /**
+ * Performs a background update check by starting an update for all add-ons
+ * that can be updated.
+ * @return Promise{null} Resolves when the background update check is complete
+ * (the resulting addon installations may still be in progress).
+ */
+ backgroundUpdateCheck: function AMI_backgroundUpdateCheck() {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ let buPromise = Task.spawn(function* backgroundUpdateTask() {
+ logger.debug("Background update check beginning");
+
+ Services.obs.notifyObservers(null, "addons-background-update-start", null);
+
+ if (this.updateEnabled) {
+ let scope = {};
+ Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
+ scope.LightweightThemeManager.updateCurrentTheme();
+
+ let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve));
+
+ // Repopulate repository cache first, to ensure compatibility overrides
+ // are up to date before checking for addon updates.
+ yield AddonRepository.backgroundUpdateCheck();
+
+ // Keep track of all the async add-on updates happening in parallel
+ let updates = [];
+
+ for (let addon of allAddons) {
+ // Check all add-ons for updates so that any compatibility updates will
+ // be applied
+ updates.push(new Promise((resolve, reject) => {
+ addon.findUpdates({
+ onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) {
+ // Start installing updates when the add-on can be updated and
+ // background updates should be applied.
+ logger.debug("Found update for add-on ${id}", aAddon);
+ if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
+ AddonManager.shouldAutoUpdate(aAddon)) {
+ // XXX we really should resolve when this install is done,
+ // not when update-available check completes, no?
+ logger.debug("Starting install of ${id}", aAddon);
+ aInstall.install();
+ }
+ },
+
+ onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); }
+ }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
+ }));
+ }
+ yield Promise.all(updates);
+ }
+
+ logger.debug("Background update check complete");
+ Services.obs.notifyObservers(null,
+ "addons-background-update-complete",
+ null);
+ }.bind(this));
+ // Fork the promise chain so we can log the error and let our caller see it too.
+ buPromise.then(null, e => logger.warn("Error in background update", e));
+ return buPromise;
+ },
+
+ /**
+ * Adds a add-on to the list of detected changes for this startup. If
+ * addStartupChange is called multiple times for the same add-on in the same
+ * startup then only the most recent change will be remembered.
+ *
+ * @param aType
+ * The type of change as a string. Providers can define their own
+ * types of changes or use the existing defined STARTUP_CHANGE_*
+ * constants
+ * @param aID
+ * The ID of the add-on
+ */
+ addStartupChange: function AMI_addStartupChange(aType, aID) {
+ if (!aType || typeof aType != "string")
+ throw Components.Exception("aType must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aID || typeof aID != "string")
+ throw Components.Exception("aID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (gStartupComplete)
+ return;
+
+ // Ensure that an ID is only listed in one type of change
+ for (let type in this.startupChanges)
+ this.removeStartupChange(type, aID);
+
+ if (!(aType in this.startupChanges))
+ this.startupChanges[aType] = [];
+ this.startupChanges[aType].push(aID);
+ },
+
+ /**
+ * Removes a startup change for an add-on.
+ *
+ * @param aType
+ * The type of change
+ * @param aID
+ * The ID of the add-on
+ */
+ removeStartupChange: function AMI_removeStartupChange(aType, aID) {
+ if (!aType || typeof aType != "string")
+ throw Components.Exception("aType must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aID || typeof aID != "string")
+ throw Components.Exception("aID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (gStartupComplete)
+ return;
+
+ if (!(aType in this.startupChanges))
+ return;
+
+ this.startupChanges[aType] = this.startupChanges[aType].filter(
+ function filterItem(aItem) aItem != aID);
+ },
+
+ /**
+ * Calls all registered AddonManagerListeners with an event. Any parameters
+ * after the method parameter are passed to the listener.
+ *
+ * @param aMethod
+ * The method on the listeners to call
+ */
+ callManagerListeners: function AMI_callManagerListeners(aMethod, ...aArgs) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let managerListeners = this.managerListeners.slice(0);
+ for (let listener of managerListeners) {
+ try {
+ if (aMethod in listener)
+ listener[aMethod].apply(listener, aArgs);
+ }
+ catch (e) {
+ logger.warn("AddonManagerListener threw exception when calling " + aMethod, e);
+ }
+ }
+ },
+
+ /**
+ * Calls all registered InstallListeners with an event. Any parameters after
+ * the extraListeners parameter are passed to the listener.
+ *
+ * @param aMethod
+ * The method on the listeners to call
+ * @param aExtraListeners
+ * An optional array of extra InstallListeners to also call
+ * @return false if any of the listeners returned false, true otherwise
+ */
+ callInstallListeners: function AMI_callInstallListeners(aMethod,
+ aExtraListeners, ...aArgs) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aExtraListeners && !Array.isArray(aExtraListeners))
+ throw Components.Exception("aExtraListeners must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let result = true;
+ let listeners;
+ if (aExtraListeners)
+ listeners = aExtraListeners.concat(this.installListeners);
+ else
+ listeners = this.installListeners.slice(0);
+
+ for (let listener of listeners) {
+ try {
+ if (aMethod in listener) {
+ if (listener[aMethod].apply(listener, aArgs) === false)
+ result = false;
+ }
+ }
+ catch (e) {
+ logger.warn("InstallListener threw exception when calling " + aMethod, e);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Calls all registered AddonListeners with an event. Any parameters after
+ * the method parameter are passed to the listener.
+ *
+ * @param aMethod
+ * The method on the listeners to call
+ */
+ callAddonListeners: function AMI_callAddonListeners(aMethod, ...aArgs) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMethod || typeof aMethod != "string")
+ throw Components.Exception("aMethod must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addonListeners = this.addonListeners.slice(0);
+ for (let listener of addonListeners) {
+ try {
+ if (aMethod in listener)
+ listener[aMethod].apply(listener, aArgs);
+ }
+ catch (e) {
+ logger.warn("AddonListener threw exception when calling " + aMethod, 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 aID
+ * The ID of the enabled add-on
+ * @param aType
+ * The type of the enabled add-on
+ * @param aPendingRestart
+ * A boolean indicating if the change will only take place the next
+ * time the application is restarted
+ */
+ notifyAddonChanged: function AMI_notifyAddonChanged(aID, aType, aPendingRestart) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aID && typeof aID != "string")
+ throw Components.Exception("aID must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aType || typeof aType != "string")
+ throw Components.Exception("aType must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ // Temporary hack until bug 520124 lands.
+ // We can get here during synchronous startup, at which point it's
+ // considered unsafe (and therefore disallowed by AddonManager.jsm) to
+ // access providers that haven't been initialized yet. Since this is when
+ // XPIProvider is starting up, XPIProvider can't access itself via APIs
+ // going through AddonManager.jsm. Furthermore, LightweightThemeManager may
+ // not be initialized until after XPIProvider is, and therefore would also
+ // be unaccessible during XPIProvider startup. Thankfully, these are the
+ // only two uses of this API, and we know it's safe to use this API with
+ // both providers; so we have this hack to allow bypassing the normal
+ // safetey guard.
+ // The notifyAddonChanged/addonChanged API will be unneeded and therefore
+ // removed by bug 520124, so this is a temporary quick'n'dirty hack.
+ let providers = [...this.providers, ...this.pendingProviders];
+ for (let provider of providers) {
+ callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart);
+ }
+ },
+
+ /**
+ * Notifies all providers they need to update the appDisabled property for
+ * their add-ons in response to an application change such as a blocklist
+ * update.
+ */
+ updateAddonAppDisabledStates: function AMI_updateAddonAppDisabledStates() {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ this.callProviders("updateAddonAppDisabledStates");
+ },
+
+ /**
+ * Notifies all providers that the repository has updated its data for
+ * installed add-ons.
+ *
+ * @param aCallback
+ * Function to call when operation is complete.
+ */
+ updateAddonRepositoryData: function AMI_updateAddonRepositoryData(aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", {
+ nextObject: function updateAddonRepositoryData_nextObject(aCaller, aProvider) {
+ callProviderAsync(aProvider, "updateAddonRepositoryData",
+ aCaller.callNext.bind(aCaller));
+ },
+ noMoreObjects: function updateAddonRepositoryData_noMoreObjects(aCaller) {
+ safeCall(aCallback);
+ // only tests should care about this
+ Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets an AddonInstall for a URL.
+ *
+ * @param aUrl
+ * The string represenation of the URL the add-on is located at
+ * @param aCallback
+ * A callback to pass the AddonInstall to
+ * @param aMimetype
+ * The mimetype of the add-on
+ * @param aHash
+ * An optional hash of the add-on
+ * @param aName
+ * An optional placeholder name while the add-on is being downloaded
+ * @param aIcons
+ * Optional placeholder icons while the add-on is being downloaded
+ * @param aVersion
+ * An optional placeholder version while the add-on is being downloaded
+ * @param aLoadGroup
+ * An optional nsILoadGroup to associate any network requests with
+ * @throws if the aUrl, aCallback or aMimetype arguments are not specified
+ */
+ getInstallForURL: function AMI_getInstallForURL(aUrl, aCallback, aMimetype,
+ aHash, aName, aIcons,
+ aVersion, aBrowser) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aUrl || typeof aUrl != "string")
+ throw Components.Exception("aURL must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aHash && typeof aHash != "string")
+ throw Components.Exception("aHash must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aName && typeof aName != "string")
+ throw Components.Exception("aName must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aIcons) {
+ if (typeof aIcons == "string")
+ aIcons = { "32": aIcons };
+ else if (typeof aIcons != "object")
+ throw Components.Exception("aIcons must be a string, an object or null",
+ Cr.NS_ERROR_INVALID_ARG);
+ } else {
+ aIcons = {};
+ }
+
+ if (aVersion && typeof aVersion != "string")
+ throw Components.Exception("aVersion must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement)))
+ throw Components.Exception("aBrowser must be a nsIDOMElement or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ if (callProvider(provider, "supportsMimetype", false, aMimetype)) {
+ callProviderAsync(provider, "getInstallForURL",
+ aUrl, aHash, aName, aIcons, aVersion, aBrowser,
+ function getInstallForURL_safeCall(aInstall) {
+ safeCall(aCallback, aInstall);
+ });
+ return;
+ }
+ }
+ safeCall(aCallback, null);
+ },
+
+ /**
+ * Asynchronously gets an AddonInstall for an nsIFile.
+ *
+ * @param aFile
+ * The nsIFile where the add-on is located
+ * @param aCallback
+ * A callback to pass the AddonInstall to
+ * @param aMimetype
+ * An optional mimetype hint for the add-on
+ * @throws if the aFile or aCallback arguments are not specified
+ */
+ getInstallForFile: function AMI_getInstallForFile(aFile, aCallback, aMimetype) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!(aFile instanceof Ci.nsIFile))
+ throw Components.Exception("aFile must be a nsIFile",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aMimetype && typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a string or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "getInstallForFile", {
+ nextObject: function getInstallForFile_nextObject(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getInstallForFile", aFile,
+ function getInstallForFile_safeCall(aInstall) {
+ if (aInstall)
+ safeCall(aCallback, aInstall);
+ else
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function getInstallForFile_noMoreObjects(aCaller) {
+ safeCall(aCallback, null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets all current AddonInstalls optionally limiting to a list
+ * of types.
+ *
+ * @param aTypes
+ * An optional array of types to retrieve. Each type is a string name
+ * @param aCallback
+ * A callback which will be passed an array of AddonInstalls
+ * @throws If the aCallback argument is not specified
+ */
+ getInstallsByTypes: function AMI_getInstallsByTypes(aTypes, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let installs = [];
+
+ new AsyncObjectCaller(this.providers, "getInstallsByTypes", {
+ nextObject: function getInstallsByTypes_nextObject(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getInstallsByTypes", aTypes,
+ function getInstallsByTypes_safeCall(aProviderInstalls) {
+ if (aProviderInstalls) {
+ installs = installs.concat(aProviderInstalls);
+ }
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function getInstallsByTypes_noMoreObjects(aCaller) {
+ safeCall(aCallback, installs);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets all current AddonInstalls.
+ *
+ * @param aCallback
+ * A callback which will be passed an array of AddonInstalls
+ */
+ getAllInstalls: function AMI_getAllInstalls(aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ this.getInstallsByTypes(null, aCallback);
+ },
+
+ /**
+ * Synchronously map a URI to the corresponding Addon ID.
+ *
+ * Mappable URIs are limited to in-application resources belonging to the
+ * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
+ * but do not include URIs from meta data, such as the add-on homepage.
+ *
+ * @param aURI
+ * nsIURI to map to an addon id
+ * @return string containing the Addon ID or null
+ * @see amIAddonManager.mapURIToAddonID
+ */
+ mapURIToAddonID: function AMI_mapURIToAddonID(aURI) {
+ if (!(aURI instanceof Ci.nsIURI)) {
+ throw Components.Exception("aURI is not a nsIURI",
+ Cr.NS_ERROR_INVALID_ARG);
+ }
+
+ // Try all providers
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ var id = callProvider(provider, "mapURIToAddonID", null, aURI);
+ if (id !== null) {
+ return id;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Checks whether installation is enabled for a particular mimetype.
+ *
+ * @param aMimetype
+ * The mimetype to check
+ * @return true if installation is enabled for the mimetype
+ */
+ isInstallEnabled: function AMI_isInstallEnabled(aMimetype) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
+ callProvider(provider, "isInstallEnabled"))
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Checks whether a particular source is allowed to install add-ons of a
+ * given mimetype.
+ *
+ * @param aMimetype
+ * The mimetype of the add-on
+ * @param aInstallingPrincipal
+ * The nsIPrincipal that initiated the install
+ * @return true if the source is allowed to install this mimetype
+ */
+ isInstallAllowed: function AMI_isInstallAllowed(aMimetype, aInstallingPrincipal) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
+ throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let providers = [...this.providers];
+ for (let provider of providers) {
+ if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
+ callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal))
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Starts installation of an array of AddonInstalls notifying the registered
+ * web install listener of blocked or started installs.
+ *
+ * @param aMimetype
+ * The mimetype of add-ons being installed
+ * @param aBrowser
+ * The optional browser element that started the installs
+ * @param aInstallingPrincipal
+ * The nsIPrincipal that initiated the install
+ * @param aInstalls
+ * The array of AddonInstalls to be installed
+ */
+ installAddonsFromWebpage: function AMI_installAddonsFromWebpage(aMimetype,
+ aBrowser,
+ aInstallingPrincipal,
+ aInstalls) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aMimetype || typeof aMimetype != "string")
+ throw Components.Exception("aMimetype must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement))
+ throw Components.Exception("aSource must be a nsIDOMElement, or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
+ throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!Array.isArray(aInstalls))
+ throw Components.Exception("aInstalls must be an array",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
+ logger.warn("No web installer available, cancelling all installs");
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ return;
+ }
+
+ // When a chrome in-content UI has loaded a <browser> inside to host a
+ // website we want to do our security checks on the inner-browser but
+ // notify front-end that install events came from the outer-browser (the
+ // main tab's browser). Check this by seeing if the browser we've been
+ // passed is in a content type docshell and if so get the outer-browser.
+ let topBrowser = aBrowser;
+ let docShell = aBrowser.ownerDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIDocShellTreeItem);
+ if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
+ topBrowser = docShell.chromeEventHandler;
+
+ try {
+ let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+
+ if (!this.isInstallEnabled(aMimetype)) {
+ for (let install of aInstalls)
+ install.cancel();
+
+ weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length);
+ return;
+ }
+ else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
+ for (let install of aInstalls)
+ install.cancel();
+
+ if (weblistener instanceof Ci.amIWebInstallListener2) {
+ weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length);
+ }
+ return;
+ }
+
+ // The installs may start now depending on the web install listener,
+ // listen for the browser navigating to a new origin and cancel the
+ // installs in that case.
+ new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls);
+
+ if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
+ if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length)) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI,
+ aInstalls, aInstalls.length)) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ catch (e) {
+ // In the event that the weblistener throws during instantiation or when
+ // calling onWebInstallBlocked or onWebInstallRequested all of the
+ // installs should get cancelled.
+ logger.warn("Failure calling web installer", e);
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ }
+ },
+
+ /**
+ * Adds a new InstallListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The InstallListener to add
+ */
+ addInstallListener: function AMI_addInstallListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a InstallListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.installListeners.some(function addInstallListener_matchListener(i) {
+ return i == aListener; }))
+ this.installListeners.push(aListener);
+ },
+
+ /**
+ * Removes an InstallListener if the listener is registered.
+ *
+ * @param aListener
+ * The InstallListener to remove
+ */
+ removeInstallListener: function AMI_removeInstallListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a InstallListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.installListeners.length) {
+ if (this.installListeners[pos] == aListener)
+ this.installListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ /**
+ * Asynchronously gets an add-on with a specific ID.
+ *
+ * @param aID
+ * The ID of the add-on to retrieve
+ * @param aCallback
+ * The callback to pass the retrieved add-on to
+ * @throws if the aID or aCallback arguments are not specified
+ */
+ getAddonByID: function AMI_getAddonByID(aID, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aID || typeof aID != "string")
+ throw Components.Exception("aID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "getAddonByID", {
+ nextObject: function getAddonByID_nextObject(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonByID", aID,
+ function getAddonByID_safeCall(aAddon) {
+ if (aAddon)
+ safeCall(aCallback, aAddon);
+ else
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function getAddonByID_noMoreObjects(aCaller) {
+ safeCall(aCallback, null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously get an add-on with a specific Sync GUID.
+ *
+ * @param aGUID
+ * String GUID of add-on to retrieve
+ * @param aCallback
+ * The callback to pass the retrieved add-on to.
+ * @throws if the aGUID or aCallback arguments are not specified
+ */
+ getAddonBySyncGUID: function AMI_getAddonBySyncGUID(aGUID, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!aGUID || typeof aGUID != "string")
+ throw Components.Exception("aGUID must be a non-empty string",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", {
+ nextObject: function getAddonBySyncGUID_nextObject(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID,
+ function getAddonBySyncGUID_safeCall(aAddon) {
+ if (aAddon) {
+ safeCall(aCallback, aAddon);
+ } else {
+ aCaller.callNext();
+ }
+ });
+ },
+
+ noMoreObjects: function getAddonBySyncGUID_noMoreObjects(aCaller) {
+ safeCall(aCallback, null);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets an array of add-ons.
+ *
+ * @param aIDs
+ * The array of IDs to retrieve
+ * @param aCallback
+ * The callback to pass an array of Addons to
+ * @throws if the aID or aCallback arguments are not specified
+ */
+ getAddonsByIDs: function AMI_getAddonsByIDs(aIDs, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (!Array.isArray(aIDs))
+ throw Components.Exception("aIDs must be an array",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addons = [];
+
+ new AsyncObjectCaller(aIDs, null, {
+ nextObject: function getAddonsByIDs_nextObject(aCaller, aID) {
+ AddonManagerInternal.getAddonByID(aID,
+ function getAddonsByIDs_getAddonByID(aAddon) {
+ addons.push(aAddon);
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function getAddonsByIDs_noMoreObjects(aCaller) {
+ safeCall(aCallback, addons);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets add-ons of specific types.
+ *
+ * @param aTypes
+ * An optional array of types to retrieve. Each type is a string name
+ * @param aCallback
+ * The callback to pass an array of Addons to.
+ * @throws if the aCallback argument is not specified
+ */
+ getAddonsByTypes: function AMI_getAddonsByTypes(aTypes, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addons = [];
+
+ new AsyncObjectCaller(this.providers, "getAddonsByTypes", {
+ nextObject: function getAddonsByTypes_nextObject(aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonsByTypes", aTypes,
+ function getAddonsByTypes_concatAddons(aProviderAddons) {
+ if (aProviderAddons) {
+ addons = addons.concat(aProviderAddons);
+ }
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function getAddonsByTypes_noMoreObjects(aCaller) {
+ safeCall(aCallback, addons);
+ }
+ });
+ },
+
+ /**
+ * Asynchronously gets all installed add-ons.
+ *
+ * @param aCallback
+ * A callback which will be passed an array of Addons
+ */
+ getAllAddons: function AMI_getAllAddons(aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ this.getAddonsByTypes(null, aCallback);
+ },
+
+ /**
+ * Asynchronously gets add-ons that have operations waiting for an application
+ * restart to complete.
+ *
+ * @param aTypes
+ * An optional array of types to retrieve. Each type is a string name
+ * @param aCallback
+ * The callback to pass the array of Addons to
+ * @throws if the aCallback argument is not specified
+ */
+ getAddonsWithOperationsByTypes:
+ function AMI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
+ if (!gStarted)
+ throw Components.Exception("AddonManager is not initialized",
+ Cr.NS_ERROR_NOT_INITIALIZED);
+
+ if (aTypes && !Array.isArray(aTypes))
+ throw Components.Exception("aTypes must be an array or null",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (typeof aCallback != "function")
+ throw Components.Exception("aCallback must be a function",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let addons = [];
+
+ new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", {
+ nextObject: function getAddonsWithOperationsByTypes_nextObject
+ (aCaller, aProvider) {
+ callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes,
+ function getAddonsWithOperationsByTypes_concatAddons
+ (aProviderAddons) {
+ if (aProviderAddons) {
+ addons = addons.concat(aProviderAddons);
+ }
+ aCaller.callNext();
+ });
+ },
+
+ noMoreObjects: function getAddonsWithOperationsByTypes_noMoreObjects(caller) {
+ safeCall(aCallback, addons);
+ }
+ });
+ },
+
+ /**
+ * Adds a new AddonManagerListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The listener to add
+ */
+ addManagerListener: function AMI_addManagerListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonManagerListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.managerListeners.some(function addManagerListener_matchListener(i) {
+ return i == aListener; }))
+ this.managerListeners.push(aListener);
+ },
+
+ /**
+ * Removes an AddonManagerListener if the listener is registered.
+ *
+ * @param aListener
+ * The listener to remove
+ */
+ removeManagerListener: function AMI_removeManagerListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonManagerListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.managerListeners.length) {
+ if (this.managerListeners[pos] == aListener)
+ this.managerListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ /**
+ * Adds a new AddonListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The AddonListener to add
+ */
+ addAddonListener: function AMI_addAddonListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.addonListeners.some(function addAddonListener_matchListener(i) {
+ return i == aListener; }))
+ this.addonListeners.push(aListener);
+ },
+
+ /**
+ * Removes an AddonListener if the listener is registered.
+ *
+ * @param aListener
+ * The AddonListener to remove
+ */
+ removeAddonListener: function AMI_removeAddonListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be an AddonListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.addonListeners.length) {
+ if (this.addonListeners[pos] == aListener)
+ this.addonListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ /**
+ * Adds a new TypeListener if the listener is not already registered.
+ *
+ * @param aListener
+ * The TypeListener to add
+ */
+ addTypeListener: function AMI_addTypeListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a TypeListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!this.typeListeners.some(function addTypeListener_matchListener(i) {
+ return i == aListener; }))
+ this.typeListeners.push(aListener);
+ },
+
+ /**
+ * Removes an TypeListener if the listener is registered.
+ *
+ * @param aListener
+ * The TypeListener to remove
+ */
+ removeTypeListener: function AMI_removeTypeListener(aListener) {
+ if (!aListener || typeof aListener != "object")
+ throw Components.Exception("aListener must be a TypeListener object",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ let pos = 0;
+ while (pos < this.typeListeners.length) {
+ if (this.typeListeners[pos] == aListener)
+ this.typeListeners.splice(pos, 1);
+ else
+ pos++;
+ }
+ },
+
+ get addonTypes() {
+ // A read-only wrapper around the types dictionary
+ return new Proxy(this.types, {
+ defineProperty(target, property, descriptor) {
+ // Not allowed to define properties
+ return false;
+ },
+
+ deleteProperty(target, property) {
+ // Not allowed to delete properties
+ return false;
+ },
+
+ get(target, property, receiver) {
+ if (!target.hasOwnProperty(property))
+ return undefined;
+
+ return target[property].type;
+ },
+
+ getOwnPropertyDescriptor(target, property) {
+ if (!target.hasOwnProperty(property))
+ return undefined;
+
+ return {
+ value: target[property].type,
+ writable: false,
+ // Claim configurability to maintain the proxy invariants.
+ configurable: true,
+ enumerable: true
+ }
+ },
+
+ preventExtensions(target) {
+ // Not allowed to prevent adding new properties
+ return false;
+ },
+
+ set(target, property, value, receiver) {
+ // Not allowed to set properties
+ return false;
+ },
+
+ setPrototypeOf(target, prototype) {
+ // Not allowed to change prototype
+ return false;
+ }
+ });
+ },
+
+ get autoUpdateDefault() {
+ return gAutoUpdateDefault;
+ },
+
+ set autoUpdateDefault(aValue) {
+ aValue = !!aValue;
+ if (aValue != gAutoUpdateDefault)
+ Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue);
+ return aValue;
+ },
+
+ get checkCompatibility() {
+ return gCheckCompatibility;
+ },
+
+ set checkCompatibility(aValue) {
+ aValue = !!aValue;
+ if (aValue != gCheckCompatibility) {
+ if (!aValue)
+ Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false);
+ else
+ Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY);
+ }
+ return aValue;
+ },
+
+ get strictCompatibility() {
+ return gStrictCompatibility;
+ },
+
+ set strictCompatibility(aValue) {
+ aValue = !!aValue;
+ if (aValue != gStrictCompatibility)
+ Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue);
+ return aValue;
+ },
+
+ get checkUpdateSecurityDefault() {
+ return gCheckUpdateSecurityDefault;
+ },
+
+ get checkUpdateSecurity() {
+ return gCheckUpdateSecurity;
+ },
+
+ set checkUpdateSecurity(aValue) {
+ aValue = !!aValue;
+ if (aValue != gCheckUpdateSecurity) {
+ if (aValue != gCheckUpdateSecurityDefault)
+ Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue);
+ else
+ Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
+ }
+ return aValue;
+ },
+
+ get updateEnabled() {
+ return gUpdateEnabled;
+ },
+
+ set updateEnabled(aValue) {
+ aValue = !!aValue;
+ if (aValue != gUpdateEnabled)
+ Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue);
+ return aValue;
+ },
+};
+
+/**
+ * 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.
+ */
+this.AddonManagerPrivate = {
+ startup: function AMP_startup() {
+ AddonManagerInternal.startup();
+ },
+
+ registerProvider: function AMP_registerProvider(aProvider, aTypes) {
+ AddonManagerInternal.registerProvider(aProvider, aTypes);
+ },
+
+ unregisterProvider: function AMP_unregisterProvider(aProvider) {
+ AddonManagerInternal.unregisterProvider(aProvider);
+ },
+
+ markProviderSafe: function AMP_markProviderSafe(aProvider) {
+ AddonManagerInternal.markProviderSafe(aProvider);
+ },
+
+ backgroundUpdateCheck: function AMP_backgroundUpdateCheck() {
+ return AddonManagerInternal.backgroundUpdateCheck();
+ },
+
+ backgroundUpdateTimerHandler() {
+ // Don't call through to the real update check if no checks are enabled.
+ if (!AddonManagerInternal.updateEnabled) {
+ logger.info("Skipping background update check");
+ return;
+ }
+ // Don't return the promise here, since the caller doesn't care.
+ AddonManagerInternal.backgroundUpdateCheck();
+ },
+
+ addStartupChange: function AMP_addStartupChange(aType, aID) {
+ AddonManagerInternal.addStartupChange(aType, aID);
+ },
+
+ removeStartupChange: function AMP_removeStartupChange(aType, aID) {
+ AddonManagerInternal.removeStartupChange(aType, aID);
+ },
+
+ notifyAddonChanged: function AMP_notifyAddonChanged(aID, aType, aPendingRestart) {
+ AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart);
+ },
+
+ updateAddonAppDisabledStates: function AMP_updateAddonAppDisabledStates() {
+ AddonManagerInternal.updateAddonAppDisabledStates();
+ },
+
+ updateAddonRepositoryData: function AMP_updateAddonRepositoryData(aCallback) {
+ AddonManagerInternal.updateAddonRepositoryData(aCallback);
+ },
+
+ callInstallListeners: function AMP_callInstallListeners(...aArgs) {
+ return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal,
+ aArgs);
+ },
+
+ callAddonListeners: function AMP_callAddonListeners(...aArgs) {
+ AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);
+ },
+
+ AddonAuthor: AddonAuthor,
+
+ AddonScreenshot: AddonScreenshot,
+
+ AddonCompatibilityOverride: AddonCompatibilityOverride,
+
+ AddonType: AddonType,
+
+ recordTimestamp: function AMP_recordTimestamp(name, value) {
+ AddonManagerInternal.recordTimestamp(name, value);
+ },
+
+ _simpleMeasures: {},
+ recordSimpleMeasure: function AMP_recordSimpleMeasure(name, value) {
+ this._simpleMeasures[name] = value;
+ },
+
+ recordException: function AMP_recordException(aModule, aContext, aException) {
+ let report = {
+ module: aModule,
+ context: aContext
+ };
+
+ if (typeof aException == "number") {
+ report.message = Components.Exception("", aException).name;
+ }
+ else {
+ report.message = aException.toString();
+ if (aException.fileName) {
+ report.file = aException.fileName;
+ report.line = aException.lineNumber;
+ }
+ }
+
+ this._simpleMeasures.exception = report;
+ },
+
+ getSimpleMeasures: function AMP_getSimpleMeasures() {
+ return this._simpleMeasures;
+ },
+
+ getTelemetryDetails: function AMP_getTelemetryDetails() {
+ return AddonManagerInternal.telemetryDetails;
+ },
+
+ setTelemetryDetails: function AMP_setTelemetryDetails(aProvider, aDetails) {
+ AddonManagerInternal.telemetryDetails[aProvider] = aDetails;
+ },
+
+ // Start a timer, record a simple measure of the time interval when
+ // timer.done() is called
+ simpleTimer: function(aName) {
+ let startTime = Cu.now();
+ return {
+ done: () => this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime))
+ };
+ },
+
+ /**
+ * Helper to call update listeners when no update is available.
+ *
+ * This can be used as an implementation for Addon.findUpdates() when
+ * no update mechanism is available.
+ */
+ callNoUpdateListeners: function (addon, listener, reason, appVersion, platformVersion) {
+ if ("onNoCompatibilityUpdateAvailable" in listener) {
+ safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon);
+ }
+ if ("onNoUpdateAvailable" in listener) {
+ safeCall(listener.onNoUpdateAvailable.bind(listener), addon);
+ }
+ if ("onUpdateFinished" in listener) {
+ safeCall(listener.onUpdateFinished.bind(listener), addon);
+ }
+ },
+};
+
+/**
+ * This is the public API that UI and developers should be calling. All methods
+ * just forward to AddonManagerInternal.
+ */
+this.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,
+ // An error occured trying to write to the filesystem.
+ ERROR_FILE_ACCESS: -4,
+ // The downloaded file seems to be WebExtension.
+ ERROR_WEBEXT_FILE: -5,
+
+ // These must be kept in sync with AddonUpdateChecker.
+ // No error was encountered.
+ UPDATE_STATUS_NO_ERROR: 0,
+ // The update check timed out
+ UPDATE_STATUS_TIMEOUT: -1,
+ // There was an error while downloading the update information.
+ UPDATE_STATUS_DOWNLOAD_ERROR: -2,
+ // The update information was malformed in some way.
+ UPDATE_STATUS_PARSE_ERROR: -3,
+ // The update information was not in any known format.
+ UPDATE_STATUS_UNKNOWN_FORMAT: -4,
+ // The update information was not correctly signed or there was an SSL error.
+ UPDATE_STATUS_SECURITY_ERROR: -5,
+ // The update was cancelled.
+ UPDATE_STATUS_CANCELLED: -6,
+
+ // 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 has no pending operations.
+ PENDING_NONE: 0,
+ // 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,
+ PENDING_UPGRADE: 16,
+
+ // Constants for operations in Addon.operationsRequiringRestart
+ // Indicates that restart isn't required for any operation.
+ OP_NEEDS_RESTART_NONE: 0,
+ // Indicates that restart is required for enabling the addon.
+ OP_NEEDS_RESTART_ENABLE: 1,
+ // Indicates that restart is required for disabling the addon.
+ OP_NEEDS_RESTART_DISABLE: 2,
+ // Indicates that restart is required for uninstalling the addon.
+ OP_NEEDS_RESTART_UNINSTALL: 4,
+ // Indicates that restart is required for installing the addon.
+ OP_NEEDS_RESTART_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,
+ // Indicates that the Addon can be set to be optionally enabled
+ // on a case-by-case basis.
+ PERM_CAN_ASK_TO_ACTIVATE: 16,
+
+ // General descriptions of where items are installed.
+ // Installed in this profile.
+ SCOPE_PROFILE: 1,
+ // Installed for all of this user's profiles.
+ SCOPE_USER: 2,
+ // Installed and owned by the application.
+ SCOPE_APPLICATION: 4,
+ // Installed for all users of the computer.
+ SCOPE_SYSTEM: 8,
+ // The combination of all scopes.
+ SCOPE_ALL: 15,
+
+ // Add-on type is expected to be displayed in the UI in a list.
+ VIEW_TYPE_LIST: "list",
+
+ // Constants describing how add-on types behave.
+
+ // If no add-ons of a type are installed, then the category for that add-on
+ // type should be hidden in the UI.
+ TYPE_UI_HIDE_EMPTY: 16,
+ // Indicates that this add-on type supports the ask-to-activate state.
+ // That is, add-ons of this type can be set to be optionally enabled
+ // on a case-by-case basis.
+ TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32,
+ // The add-on type natively supports undo for restartless uninstalls.
+ // If this flag is not specified, the UI is expected to handle this via
+ // disabling the add-on, and performing the actual uninstall at a later time.
+ TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL: 64,
+
+ // Constants for Addon.applyBackgroundUpdates.
+ // Indicates that the Addon should not update automatically.
+ AUTOUPDATE_DISABLE: 0,
+ // Indicates that the Addon should update automatically only if
+ // that's the global default.
+ AUTOUPDATE_DEFAULT: 1,
+ // Indicates that the Addon should update automatically.
+ AUTOUPDATE_ENABLE: 2,
+
+ // Constants for how Addon options should be shown.
+ // Options will be opened in a new window
+ OPTIONS_TYPE_DIALOG: 1,
+ // Options will be displayed within the AM detail view
+ OPTIONS_TYPE_INLINE: 2,
+ // Options will be displayed in a new tab, if possible
+ OPTIONS_TYPE_TAB: 3,
+ // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown.
+ // Used to indicate that only non-interactive information will be shown.
+ OPTIONS_TYPE_INLINE_INFO: 4,
+
+ // Constants for displayed or hidden options notifications
+ // Options notification will be displayed
+ OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed",
+ // Options notification will be hidden
+ OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden",
+
+ // Constants for getStartupChanges, addStartupChange and removeStartupChange
+ // Add-ons that were detected as installed during startup. Doesn't include
+ // add-ons that were pending installation the last time the application ran.
+ STARTUP_CHANGE_INSTALLED: "installed",
+ // Add-ons that were detected as changed during startup. This includes an
+ // add-on moving to a different location, changing version or just having
+ // been detected as possibly changed.
+ STARTUP_CHANGE_CHANGED: "changed",
+ // Add-ons that were detected as uninstalled during startup. Doesn't include
+ // add-ons that were pending uninstallation the last time the application ran.
+ STARTUP_CHANGE_UNINSTALLED: "uninstalled",
+ // Add-ons that were detected as disabled during startup, normally because of
+ // an application change making an add-on incompatible. Doesn't include
+ // add-ons that were pending being disabled the last time the application ran.
+ STARTUP_CHANGE_DISABLED: "disabled",
+ // Add-ons that were detected as enabled during startup, normally because of
+ // an application change making an add-on compatible. Doesn't include
+ // add-ons that were pending being enabled the last time the application ran.
+ STARTUP_CHANGE_ENABLED: "enabled",
+
+ // Constants for the Addon.userDisabled property
+ // Indicates that the userDisabled state of this add-on is currently
+ // ask-to-activate. That is, it can be conditionally enabled on a
+ // case-by-case basis.
+ STATE_ASK_TO_ACTIVATE: "askToActivate",
+
+#ifdef MOZ_EM_DEBUG
+ get __AddonManagerInternal__() {
+ return AddonManagerInternal;
+ },
+#endif
+
+ get isReady() {
+ return gStartupComplete && !gShutdownInProgress;
+ },
+
+ getInstallForURL: function AM_getInstallForURL(aUrl, aCallback, aMimetype,
+ aHash, aName, aIcons,
+ aVersion, aBrowser) {
+ AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash,
+ aName, aIcons, aVersion, aBrowser);
+ },
+
+ getInstallForFile: function AM_getInstallForFile(aFile, aCallback, aMimetype) {
+ AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype);
+ },
+
+ /**
+ * Gets an array of add-on IDs that changed during the most recent startup.
+ *
+ * @param aType
+ * The type of startup change to get
+ * @return An array of add-on IDs
+ */
+ getStartupChanges: function AM_getStartupChanges(aType) {
+ if (!(aType in AddonManagerInternal.startupChanges))
+ return [];
+ return AddonManagerInternal.startupChanges[aType].slice(0);
+ },
+
+ getAddonByID: function AM_getAddonByID(aID, aCallback) {
+ AddonManagerInternal.getAddonByID(aID, aCallback);
+ },
+
+ getAddonBySyncGUID: function AM_getAddonBySyncGUID(aGUID, aCallback) {
+ AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback);
+ },
+
+ getAddonsByIDs: function AM_getAddonsByIDs(aIDs, aCallback) {
+ AddonManagerInternal.getAddonsByIDs(aIDs, aCallback);
+ },
+
+ getAddonsWithOperationsByTypes:
+ function AM_getAddonsWithOperationsByTypes(aTypes, aCallback) {
+ AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback);
+ },
+
+ getAddonsByTypes: function AM_getAddonsByTypes(aTypes, aCallback) {
+ AddonManagerInternal.getAddonsByTypes(aTypes, aCallback);
+ },
+
+ getAllAddons: function AM_getAllAddons(aCallback) {
+ AddonManagerInternal.getAllAddons(aCallback);
+ },
+
+ getInstallsByTypes: function AM_getInstallsByTypes(aTypes, aCallback) {
+ AddonManagerInternal.getInstallsByTypes(aTypes, aCallback);
+ },
+
+ getAllInstalls: function AM_getAllInstalls(aCallback) {
+ AddonManagerInternal.getAllInstalls(aCallback);
+ },
+
+ mapURIToAddonID: function AM_mapURIToAddonID(aURI) {
+ return AddonManagerInternal.mapURIToAddonID(aURI);
+ },
+
+ isInstallEnabled: function AM_isInstallEnabled(aType) {
+ return AddonManagerInternal.isInstallEnabled(aType);
+ },
+
+ isInstallAllowed: function AM_isInstallAllowed(aType, aInstallingPrincipal) {
+ return AddonManagerInternal.isInstallAllowed(aType, ensurePrincipal(aInstallingPrincipal));
+ },
+
+ installAddonsFromWebpage: function AM_installAddonsFromWebpage(aType, aBrowser,
+ aInstallingPrincipal,
+ aInstalls) {
+ AddonManagerInternal.installAddonsFromWebpage(aType, aBrowser,
+ ensurePrincipal(aInstallingPrincipal),
+ aInstalls);
+ },
+
+ addManagerListener: function AM_addManagerListener(aListener) {
+ AddonManagerInternal.addManagerListener(aListener);
+ },
+
+ removeManagerListener: function AM_removeManagerListener(aListener) {
+ AddonManagerInternal.removeManagerListener(aListener);
+ },
+
+ addInstallListener: function AM_addInstallListener(aListener) {
+ AddonManagerInternal.addInstallListener(aListener);
+ },
+
+ removeInstallListener: function AM_removeInstallListener(aListener) {
+ AddonManagerInternal.removeInstallListener(aListener);
+ },
+
+ addAddonListener: function AM_addAddonListener(aListener) {
+ AddonManagerInternal.addAddonListener(aListener);
+ },
+
+ removeAddonListener: function AM_removeAddonListener(aListener) {
+ AddonManagerInternal.removeAddonListener(aListener);
+ },
+
+ addTypeListener: function AM_addTypeListener(aListener) {
+ AddonManagerInternal.addTypeListener(aListener);
+ },
+
+ removeTypeListener: function AM_removeTypeListener(aListener) {
+ AddonManagerInternal.removeTypeListener(aListener);
+ },
+
+ get addonTypes() {
+ return AddonManagerInternal.addonTypes;
+ },
+
+ /**
+ * Determines whether an Addon should auto-update or not.
+ *
+ * @param aAddon
+ * The Addon representing the add-on
+ * @return true if the addon should auto-update, false otherwise.
+ */
+ shouldAutoUpdate: function AM_shouldAutoUpdate(aAddon) {
+ if (!aAddon || typeof aAddon != "object")
+ throw Components.Exception("aAddon must be specified",
+ Cr.NS_ERROR_INVALID_ARG);
+
+ if (!("applyBackgroundUpdates" in aAddon))
+ return false;
+ if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE)
+ return true;
+ if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE)
+ return false;
+ return this.autoUpdateDefault;
+ },
+
+ get checkCompatibility() {
+ return AddonManagerInternal.checkCompatibility;
+ },
+
+ set checkCompatibility(aValue) {
+ AddonManagerInternal.checkCompatibility = aValue;
+ },
+
+ get strictCompatibility() {
+ return AddonManagerInternal.strictCompatibility;
+ },
+
+ set strictCompatibility(aValue) {
+ AddonManagerInternal.strictCompatibility = aValue;
+ },
+
+ get checkUpdateSecurityDefault() {
+ return AddonManagerInternal.checkUpdateSecurityDefault;
+ },
+
+ get checkUpdateSecurity() {
+ return AddonManagerInternal.checkUpdateSecurity;
+ },
+
+ set checkUpdateSecurity(aValue) {
+ AddonManagerInternal.checkUpdateSecurity = aValue;
+ },
+
+ get updateEnabled() {
+ return AddonManagerInternal.updateEnabled;
+ },
+
+ set updateEnabled(aValue) {
+ AddonManagerInternal.updateEnabled = aValue;
+ },
+
+ get autoUpdateDefault() {
+ return AddonManagerInternal.autoUpdateDefault;
+ },
+
+ set autoUpdateDefault(aValue) {
+ AddonManagerInternal.autoUpdateDefault = aValue;
+ },
+
+ escapeAddonURI: function AM_escapeAddonURI(aAddon, aUri, aAppVersion) {
+ return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion);
+ },
+
+ get shutdown() {
+ return gShutdownBarrier.client;
+ },
+};
+
+// load the timestamps module into AddonManagerInternal
+Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal);
+Object.freeze(AddonManagerInternal);
+Object.freeze(AddonManagerPrivate);
+Object.freeze(AddonManager);