michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: // Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as michael@0: // most tests later register different nsIAppInfo implementations, which michael@0: // wouldn't be reflected in Services.appinfo anymore, as the lazy getter michael@0: // underlying it would have been initialized if we used it here. michael@0: if ("@mozilla.org/xre/app-info;1" in Cc) { michael@0: let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime); michael@0: if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) { michael@0: // Refuse to run in child processes. michael@0: throw new Error("You cannot use the AddonManager in child processes!"); michael@0: } michael@0: } michael@0: michael@0: michael@0: const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion"; michael@0: const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled"; michael@0: const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled"; michael@0: const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion"; michael@0: const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion"; michael@0: const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault"; michael@0: const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; michael@0: const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; michael@0: const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url"; michael@0: const PREF_APP_UPDATE_ENABLED = "app.update.enabled"; michael@0: const PREF_APP_UPDATE_AUTO = "app.update.auto"; michael@0: const PREF_EM_HOTFIX_ID = "extensions.hotfix.id"; michael@0: const PREF_EM_HOTFIX_LASTVERSION = "extensions.hotfix.lastVersion"; michael@0: const PREF_EM_HOTFIX_URL = "extensions.hotfix.url"; michael@0: const PREF_EM_CERT_CHECKATTRIBUTES = "extensions.hotfix.cert.checkAttributes"; michael@0: const PREF_EM_HOTFIX_CERTS = "extensions.hotfix.certs."; michael@0: const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS"; michael@0: const PREF_SELECTED_LOCALE = "general.useragent.locale"; michael@0: const UNKNOWN_XPCOM_ABI = "unknownABI"; michael@0: michael@0: const UPDATE_REQUEST_VERSION = 2; michael@0: const CATEGORY_UPDATE_PARAMS = "extension-update-params"; michael@0: michael@0: const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist"; michael@0: michael@0: const KEY_PROFILEDIR = "ProfD"; michael@0: const KEY_APPDIR = "XCurProcD"; michael@0: const FILE_BLOCKLIST = "blocklist.xml"; michael@0: michael@0: const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi; michael@0: const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility"; michael@0: #ifdef MOZ_COMPATIBILITY_NIGHTLY michael@0: var PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly"; michael@0: #else michael@0: var PREF_EM_CHECK_COMPATIBILITY; michael@0: #endif michael@0: michael@0: const TOOLKIT_ID = "toolkit@mozilla.org"; michael@0: michael@0: const VALID_TYPES_REGEXP = /^[\w\-]+$/; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/AsyncShutdown.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository", michael@0: "resource://gre/modules/addons/AddonRepository.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() { michael@0: let certUtils = {}; michael@0: Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils); michael@0: return certUtils; michael@0: }); michael@0: michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ]; michael@0: michael@0: const CATEGORY_PROVIDER_MODULE = "addon-provider-module"; michael@0: michael@0: // A list of providers to load by default michael@0: const DEFAULT_PROVIDERS = [ michael@0: "resource://gre/modules/addons/XPIProvider.jsm", michael@0: "resource://gre/modules/LightweightThemeManager.jsm" michael@0: ]; michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: // Configure a logger at the parent 'addons' level to format michael@0: // messages for all the modules under addons.* michael@0: const PARENT_LOGGER_ID = "addons"; michael@0: let parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID); michael@0: parentLogger.level = Log.Level.Warn; michael@0: let formatter = new Log.BasicFormatter(); michael@0: // Set parent logger (and its children) to append to michael@0: // the Javascript section of the Browser Console michael@0: parentLogger.addAppender(new Log.ConsoleAppender(formatter)); michael@0: // Set parent logger (and its children) to michael@0: // also append to standard out michael@0: parentLogger.addAppender(new Log.DumpAppender(formatter)); michael@0: michael@0: // Create a new logger (child of 'addons' logger) michael@0: // for use by the Addons Manager michael@0: const LOGGER_ID = "addons.manager"; michael@0: let logger = Log.repository.getLogger(LOGGER_ID); michael@0: michael@0: // Provide the ability to enable/disable logging michael@0: // messages at runtime. michael@0: // If the "extensions.logging.enabled" preference is michael@0: // missing or 'false', messages at the WARNING and higher michael@0: // severity should be logged to the JS console and standard error. michael@0: // If "extensions.logging.enabled" is set to 'true', messages michael@0: // at DEBUG and higher should go to JS console and standard error. michael@0: const PREF_LOGGING_ENABLED = "extensions.logging.enabled"; michael@0: const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; michael@0: michael@0: /** michael@0: * Preference listener which listens for a change in the michael@0: * "extensions.logging.enabled" preference and changes the logging level of the michael@0: * parent 'addons' level logger accordingly. michael@0: */ michael@0: var PrefObserver = { michael@0: init: function PrefObserver_init() { michael@0: Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false); michael@0: Services.obs.addObserver(this, "xpcom-shutdown", false); michael@0: this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED); michael@0: }, michael@0: michael@0: observe: function PrefObserver_observe(aSubject, aTopic, aData) { michael@0: if (aTopic == "xpcom-shutdown") { michael@0: Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this); michael@0: Services.obs.removeObserver(this, "xpcom-shutdown"); michael@0: } michael@0: else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) { michael@0: let debugLogEnabled = false; michael@0: try { michael@0: debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED); michael@0: } michael@0: catch (e) { michael@0: } michael@0: if (debugLogEnabled) { michael@0: parentLogger.level = Log.Level.Debug; michael@0: } michael@0: else { michael@0: parentLogger.level = Log.Level.Warn; michael@0: } michael@0: } michael@0: } michael@0: }; michael@0: michael@0: PrefObserver.init(); michael@0: michael@0: /** michael@0: * Calls a callback method consuming any thrown exception. Any parameters after michael@0: * the callback parameter will be passed to the callback. michael@0: * michael@0: * @param aCallback michael@0: * The callback method to call michael@0: */ michael@0: function safeCall(aCallback, ...aArgs) { michael@0: try { michael@0: aCallback.apply(null, aArgs); michael@0: } michael@0: catch (e) { michael@0: logger.warn("Exception calling callback", e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Calls a method on a provider if it exists and consumes any thrown exception. michael@0: * Any parameters after the dflt parameter are passed to the provider's method. michael@0: * michael@0: * @param aProvider michael@0: * The provider to call michael@0: * @param aMethod michael@0: * The method name to call michael@0: * @param aDefault michael@0: * A default return value if the provider does not implement the named michael@0: * method or throws an error. michael@0: * @return the return value from the provider or dflt if the provider does not michael@0: * implement method or throws an error michael@0: */ michael@0: function callProvider(aProvider, aMethod, aDefault, ...aArgs) { michael@0: if (!(aMethod in aProvider)) michael@0: return aDefault; michael@0: michael@0: try { michael@0: return aProvider[aMethod].apply(aProvider, aArgs); michael@0: } michael@0: catch (e) { michael@0: logger.error("Exception calling provider " + aMethod, e); michael@0: return aDefault; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Gets the currently selected locale for display. michael@0: * @return the selected locale or "en-US" if none is selected michael@0: */ michael@0: function getLocale() { michael@0: try { michael@0: if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE)) michael@0: return Services.locale.getLocaleComponentForUserAgent(); michael@0: } michael@0: catch (e) { } michael@0: michael@0: try { michael@0: let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, michael@0: Ci.nsIPrefLocalizedString); michael@0: if (locale) michael@0: return locale; michael@0: } michael@0: catch (e) { } michael@0: michael@0: try { michael@0: return Services.prefs.getCharPref(PREF_SELECTED_LOCALE); michael@0: } michael@0: catch (e) { } michael@0: michael@0: return "en-US"; michael@0: } michael@0: michael@0: /** michael@0: * A helper class to repeatedly call a listener with each object in an array michael@0: * optionally checking whether the object has a method in it. michael@0: * michael@0: * @param aObjects michael@0: * The array of objects to iterate through michael@0: * @param aMethod michael@0: * An optional method name, if not null any objects without this method michael@0: * will not be passed to the listener michael@0: * @param aListener michael@0: * A listener implementing nextObject and noMoreObjects methods. The michael@0: * former will be called with the AsyncObjectCaller as the first michael@0: * parameter and the object as the second. noMoreObjects will be passed michael@0: * just the AsyncObjectCaller michael@0: */ michael@0: function AsyncObjectCaller(aObjects, aMethod, aListener) { michael@0: this.objects = aObjects.slice(0); michael@0: this.method = aMethod; michael@0: this.listener = aListener; michael@0: michael@0: this.callNext(); michael@0: } michael@0: michael@0: AsyncObjectCaller.prototype = { michael@0: objects: null, michael@0: method: null, michael@0: listener: null, michael@0: michael@0: /** michael@0: * Passes the next object to the listener or calls noMoreObjects if there michael@0: * are none left. michael@0: */ michael@0: callNext: function AOC_callNext() { michael@0: if (this.objects.length == 0) { michael@0: this.listener.noMoreObjects(this); michael@0: return; michael@0: } michael@0: michael@0: let object = this.objects.shift(); michael@0: if (!this.method || this.method in object) michael@0: this.listener.nextObject(this, object); michael@0: else michael@0: this.callNext(); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * This represents an author of an add-on (e.g. creator or developer) michael@0: * michael@0: * @param aName michael@0: * The name of the author michael@0: * @param aURL michael@0: * The URL of the author's profile page michael@0: */ michael@0: function AddonAuthor(aName, aURL) { michael@0: this.name = aName; michael@0: this.url = aURL; michael@0: } michael@0: michael@0: AddonAuthor.prototype = { michael@0: name: null, michael@0: url: null, michael@0: michael@0: // Returns the author's name, defaulting to the empty string michael@0: toString: function AddonAuthor_toString() { michael@0: return this.name || ""; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This represents an screenshot for an add-on michael@0: * michael@0: * @param aURL michael@0: * The URL to the full version of the screenshot michael@0: * @param aWidth michael@0: * The width in pixels of the screenshot michael@0: * @param aHeight michael@0: * The height in pixels of the screenshot michael@0: * @param aThumbnailURL michael@0: * The URL to the thumbnail version of the screenshot michael@0: * @param aThumbnailWidth michael@0: * The width in pixels of the thumbnail version of the screenshot michael@0: * @param aThumbnailHeight michael@0: * The height in pixels of the thumbnail version of the screenshot michael@0: * @param aCaption michael@0: * The caption of the screenshot michael@0: */ michael@0: function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL, michael@0: aThumbnailWidth, aThumbnailHeight, aCaption) { michael@0: this.url = aURL; michael@0: if (aWidth) this.width = aWidth; michael@0: if (aHeight) this.height = aHeight; michael@0: if (aThumbnailURL) this.thumbnailURL = aThumbnailURL; michael@0: if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth; michael@0: if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight; michael@0: if (aCaption) this.caption = aCaption; michael@0: } michael@0: michael@0: AddonScreenshot.prototype = { michael@0: url: null, michael@0: width: null, michael@0: height: null, michael@0: thumbnailURL: null, michael@0: thumbnailWidth: null, michael@0: thumbnailHeight: null, michael@0: caption: null, michael@0: michael@0: // Returns the screenshot URL, defaulting to the empty string michael@0: toString: function AddonScreenshot_toString() { michael@0: return this.url || ""; michael@0: } michael@0: } michael@0: michael@0: michael@0: /** michael@0: * This represents a compatibility override for an addon. michael@0: * michael@0: * @param aType michael@0: * Overrride type - "compatible" or "incompatible" michael@0: * @param aMinVersion michael@0: * Minimum version of the addon to match michael@0: * @param aMaxVersion michael@0: * Maximum version of the addon to match michael@0: * @param aAppID michael@0: * Application ID used to match appMinVersion and appMaxVersion michael@0: * @param aAppMinVersion michael@0: * Minimum version of the application to match michael@0: * @param aAppMaxVersion michael@0: * Maximum version of the application to match michael@0: */ michael@0: function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID, michael@0: aAppMinVersion, aAppMaxVersion) { michael@0: this.type = aType; michael@0: this.minVersion = aMinVersion; michael@0: this.maxVersion = aMaxVersion; michael@0: this.appID = aAppID; michael@0: this.appMinVersion = aAppMinVersion; michael@0: this.appMaxVersion = aAppMaxVersion; michael@0: } michael@0: michael@0: AddonCompatibilityOverride.prototype = { michael@0: /** michael@0: * Type of override - "incompatible" or "compatible". michael@0: * Only "incompatible" is supported for now. michael@0: */ michael@0: type: null, michael@0: michael@0: /** michael@0: * Min version of the addon to match. michael@0: */ michael@0: minVersion: null, michael@0: michael@0: /** michael@0: * Max version of the addon to match. michael@0: */ michael@0: maxVersion: null, michael@0: michael@0: /** michael@0: * Application ID to match. michael@0: */ michael@0: appID: null, michael@0: michael@0: /** michael@0: * Min version of the application to match. michael@0: */ michael@0: appMinVersion: null, michael@0: michael@0: /** michael@0: * Max version of the application to match. michael@0: */ michael@0: appMaxVersion: null michael@0: }; michael@0: michael@0: michael@0: /** michael@0: * A type of add-on, used by the UI to determine how to display different types michael@0: * of add-ons. michael@0: * michael@0: * @param aID michael@0: * The add-on type ID michael@0: * @param aLocaleURI michael@0: * The URI of a localized properties file to get the displayable name michael@0: * for the type from michael@0: * @param aLocaleKey michael@0: * The key for the string in the properties file or the actual display michael@0: * name if aLocaleURI is null. Include %ID% to include the type ID in michael@0: * the key michael@0: * @param aViewType michael@0: * The optional type of view to use in the UI michael@0: * @param aUIPriority michael@0: * The priority is used by the UI to list the types in order. Lower michael@0: * values push the type higher in the list. michael@0: * @param aFlags michael@0: * An option set of flags that customize the display of the add-on in michael@0: * the UI. michael@0: */ michael@0: function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) { michael@0: if (!aID) michael@0: throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aViewType && aUIPriority === undefined) michael@0: throw Components.Exception("An AddonType with a defined view must have a set UI priority", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!aLocaleKey) michael@0: throw Components.Exception("An AddonType must have a displayable name", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: this.id = aID; michael@0: this.uiPriority = aUIPriority; michael@0: this.viewType = aViewType; michael@0: this.flags = aFlags; michael@0: michael@0: if (aLocaleURI) { michael@0: this.__defineGetter__("name", function nameGetter() { michael@0: delete this.name; michael@0: let bundle = Services.strings.createBundle(aLocaleURI); michael@0: this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID)); michael@0: return this.name; michael@0: }); michael@0: } michael@0: else { michael@0: this.name = aLocaleKey; michael@0: } michael@0: } michael@0: michael@0: var gStarted = false; michael@0: var gStartupComplete = false; michael@0: var gCheckCompatibility = true; michael@0: var gStrictCompatibility = true; michael@0: var gCheckUpdateSecurityDefault = true; michael@0: var gCheckUpdateSecurity = gCheckUpdateSecurityDefault; michael@0: var gUpdateEnabled = true; michael@0: var gAutoUpdateDefault = true; michael@0: var gHotfixID = null; michael@0: michael@0: /** michael@0: * This is the real manager, kept here rather than in AddonManager to keep its michael@0: * contents hidden from API users. michael@0: */ michael@0: var AddonManagerInternal = { michael@0: managerListeners: [], michael@0: installListeners: [], michael@0: addonListeners: [], michael@0: typeListeners: [], michael@0: providers: [], michael@0: types: {}, michael@0: startupChanges: {}, michael@0: // Store telemetry details per addon provider michael@0: telemetryDetails: {}, michael@0: michael@0: michael@0: // A read-only wrapper around the types dictionary michael@0: typesProxy: Proxy.create({ michael@0: getOwnPropertyDescriptor: function typesProxy_getOwnPropertyDescriptor(aName) { michael@0: if (!(aName in AddonManagerInternal.types)) michael@0: return undefined; michael@0: michael@0: return { michael@0: value: AddonManagerInternal.types[aName].type, michael@0: writable: false, michael@0: configurable: false, michael@0: enumerable: true michael@0: } michael@0: }, michael@0: michael@0: getPropertyDescriptor: function typesProxy_getPropertyDescriptor(aName) { michael@0: return this.getOwnPropertyDescriptor(aName); michael@0: }, michael@0: michael@0: getOwnPropertyNames: function typesProxy_getOwnPropertyNames() { michael@0: return Object.keys(AddonManagerInternal.types); michael@0: }, michael@0: michael@0: getPropertyNames: function typesProxy_getPropertyNames() { michael@0: return this.getOwnPropertyNames(); michael@0: }, michael@0: michael@0: delete: function typesProxy_delete(aName) { michael@0: // Not allowed to delete properties michael@0: return false; michael@0: }, michael@0: michael@0: defineProperty: function typesProxy_defineProperty(aName, aProperty) { michael@0: // Ignore attempts to define properties michael@0: }, michael@0: michael@0: fix: function typesProxy_fix(){ michael@0: return undefined; michael@0: }, michael@0: michael@0: // Despite MDC's claims to the contrary, it is required that this trap michael@0: // be defined michael@0: enumerate: function typesProxy_enumerate() { michael@0: // All properties are enumerable michael@0: return this.getPropertyNames(); michael@0: } michael@0: }), michael@0: michael@0: recordTimestamp: function AMI_recordTimestamp(name, value) { michael@0: this.TelemetryTimestamps.add(name, value); michael@0: }, michael@0: michael@0: validateBlocklist: function AMI_validateBlocklist() { michael@0: let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]); michael@0: michael@0: // If there is no application shipped blocklist then there is nothing to do michael@0: if (!appBlocklist.exists()) michael@0: return; michael@0: michael@0: let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]); michael@0: michael@0: // If there is no blocklist in the profile then copy the application shipped michael@0: // one there michael@0: if (!profileBlocklist.exists()) { michael@0: try { michael@0: appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); michael@0: } michael@0: catch (e) { michael@0: logger.warn("Failed to copy the application shipped blocklist to the profile", e); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: let fileStream = Cc["@mozilla.org/network/file-input-stream;1"]. michael@0: createInstance(Ci.nsIFileInputStream); michael@0: try { michael@0: let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]. michael@0: createInstance(Ci.nsIConverterInputStream); michael@0: fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); michael@0: cstream.init(fileStream, "UTF-8", 0, 0); michael@0: michael@0: let data = ""; michael@0: let str = {}; michael@0: let read = 0; michael@0: do { michael@0: read = cstream.readString(0xffffffff, str); michael@0: data += str.value; michael@0: } while (read != 0); michael@0: michael@0: let parser = Cc["@mozilla.org/xmlextras/domparser;1"]. michael@0: createInstance(Ci.nsIDOMParser); michael@0: var doc = parser.parseFromString(data, "text/xml"); michael@0: } michael@0: catch (e) { michael@0: logger.warn("Application shipped blocklist could not be loaded", e); michael@0: return; michael@0: } michael@0: finally { michael@0: try { michael@0: fileStream.close(); michael@0: } michael@0: catch (e) { michael@0: logger.warn("Unable to close blocklist file stream", e); michael@0: } michael@0: } michael@0: michael@0: // If the namespace is incorrect then ignore the application shipped michael@0: // blocklist michael@0: if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) { michael@0: logger.warn("Application shipped blocklist has an unexpected namespace (" + michael@0: doc.documentElement.namespaceURI + ")"); michael@0: return; michael@0: } michael@0: michael@0: // If there is no lastupdate information then ignore the application shipped michael@0: // blocklist michael@0: if (!doc.documentElement.hasAttribute("lastupdate")) michael@0: return; michael@0: michael@0: // If the application shipped blocklist is older than the profile blocklist michael@0: // then do nothing michael@0: if (doc.documentElement.getAttribute("lastupdate") <= michael@0: profileBlocklist.lastModifiedTime) michael@0: return; michael@0: michael@0: // Otherwise copy the application shipped blocklist to the profile michael@0: try { michael@0: appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST); michael@0: } michael@0: catch (e) { michael@0: logger.warn("Failed to copy the application shipped blocklist to the profile", e); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Initializes the AddonManager, loading any known providers and initializing michael@0: * them. michael@0: */ michael@0: startup: function AMI_startup() { michael@0: try { michael@0: if (gStarted) michael@0: return; michael@0: michael@0: this.recordTimestamp("AMI_startup_begin"); michael@0: michael@0: // clear this for xpcshell test restarts michael@0: for (let provider in this.telemetryDetails) michael@0: delete this.telemetryDetails[provider]; michael@0: michael@0: let appChanged = undefined; michael@0: michael@0: let oldAppVersion = null; michael@0: try { michael@0: oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION); michael@0: appChanged = Services.appinfo.version != oldAppVersion; michael@0: } michael@0: catch (e) { } michael@0: michael@0: let oldPlatformVersion = null; michael@0: try { michael@0: oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION); michael@0: } michael@0: catch (e) { } michael@0: michael@0: if (appChanged !== false) { michael@0: logger.debug("Application has been upgraded"); michael@0: Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION, michael@0: Services.appinfo.version); michael@0: Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION, michael@0: Services.appinfo.platformVersion); michael@0: Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION, michael@0: (appChanged === undefined ? 0 : -1)); michael@0: this.validateBlocklist(); michael@0: } michael@0: michael@0: #ifndef MOZ_COMPATIBILITY_NIGHTLY michael@0: PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." + michael@0: Services.appinfo.version.replace(BRANCH_REGEXP, "$1"); michael@0: #endif michael@0: michael@0: try { michael@0: gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); michael@0: } catch (e) {} michael@0: Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false); michael@0: michael@0: try { michael@0: gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); michael@0: } catch (e) {} michael@0: Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false); michael@0: michael@0: try { michael@0: let defaultBranch = Services.prefs.getDefaultBranch(""); michael@0: gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); michael@0: } catch(e) {} michael@0: michael@0: try { michael@0: gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); michael@0: } catch (e) {} michael@0: Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false); michael@0: michael@0: try { michael@0: gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); michael@0: } catch (e) {} michael@0: Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false); michael@0: michael@0: try { michael@0: gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); michael@0: } catch (e) {} michael@0: Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false); michael@0: michael@0: try { michael@0: gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); michael@0: } catch (e) {} michael@0: Services.prefs.addObserver(PREF_EM_HOTFIX_ID, this, false); michael@0: michael@0: let defaultProvidersEnabled = true; michael@0: try { michael@0: defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED); michael@0: } catch (e) {} michael@0: AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled); michael@0: michael@0: // Ensure all default providers have had a chance to register themselves michael@0: if (defaultProvidersEnabled) { michael@0: for (let url of DEFAULT_PROVIDERS) { michael@0: try { michael@0: let scope = {}; michael@0: Components.utils.import(url, scope); michael@0: // Sanity check - make sure the provider exports a symbol that michael@0: // has a 'startup' method michael@0: let syms = Object.keys(scope); michael@0: if ((syms.length < 1) || michael@0: (typeof scope[syms[0]].startup != "function")) { michael@0: logger.warn("Provider " + url + " has no startup()"); michael@0: AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()"); michael@0: } michael@0: logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource()); michael@0: } michael@0: catch (e) { michael@0: AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); michael@0: logger.error("Exception loading default provider \"" + url + "\"", e); michael@0: } michael@0: }; michael@0: } michael@0: michael@0: // Load any providers registered in the category manager michael@0: let catman = Cc["@mozilla.org/categorymanager;1"]. michael@0: getService(Ci.nsICategoryManager); michael@0: let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE); michael@0: while (entries.hasMoreElements()) { michael@0: let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data; michael@0: let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry); michael@0: michael@0: try { michael@0: Components.utils.import(url, {}); michael@0: } michael@0: catch (e) { michael@0: AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e); michael@0: logger.error("Exception loading provider " + entry + " from category \"" + michael@0: url + "\"", e); michael@0: } michael@0: } michael@0: michael@0: // Register our shutdown handler with the AsyncShutdown manager michael@0: AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down providers", michael@0: this.shutdown.bind(this)); michael@0: michael@0: // Once we start calling providers we must allow all normal methods to work. michael@0: gStarted = true; michael@0: michael@0: this.callProviders("startup", appChanged, oldAppVersion, michael@0: oldPlatformVersion); michael@0: michael@0: // If this is a new profile just pretend that there were no changes michael@0: if (appChanged === undefined) { michael@0: for (let type in this.startupChanges) michael@0: delete this.startupChanges[type]; michael@0: } michael@0: michael@0: gStartupComplete = true; michael@0: this.recordTimestamp("AMI_startup_end"); michael@0: } michael@0: catch (e) { michael@0: logger.error("startup failed", e); michael@0: AddonManagerPrivate.recordException("AMI", "startup failed", e); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Registers a new AddonProvider. michael@0: * michael@0: * @param aProvider michael@0: * The provider to register michael@0: * @param aTypes michael@0: * An optional array of add-on types michael@0: */ michael@0: registerProvider: function AMI_registerProvider(aProvider, aTypes) { michael@0: if (!aProvider || typeof aProvider != "object") michael@0: throw Components.Exception("aProvider must be specified", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aTypes && !Array.isArray(aTypes)) michael@0: throw Components.Exception("aTypes must be an array or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: this.providers.push(aProvider); michael@0: michael@0: if (aTypes) { michael@0: aTypes.forEach(function(aType) { michael@0: if (!(aType.id in this.types)) { michael@0: if (!VALID_TYPES_REGEXP.test(aType.id)) { michael@0: logger.warn("Ignoring invalid type " + aType.id); michael@0: return; michael@0: } michael@0: michael@0: this.types[aType.id] = { michael@0: type: aType, michael@0: providers: [aProvider] michael@0: }; michael@0: michael@0: let typeListeners = this.typeListeners.slice(0); michael@0: for (let listener of typeListeners) { michael@0: safeCall(function listenerSafeCall() { michael@0: listener.onTypeAdded(aType); michael@0: }); michael@0: } michael@0: } michael@0: else { michael@0: this.types[aType.id].providers.push(aProvider); michael@0: } michael@0: }, this); michael@0: } michael@0: michael@0: // If we're registering after startup call this provider's startup. michael@0: if (gStarted) michael@0: callProvider(aProvider, "startup"); michael@0: }, michael@0: michael@0: /** michael@0: * Unregisters an AddonProvider. michael@0: * michael@0: * @param aProvider michael@0: * The provider to unregister michael@0: */ michael@0: unregisterProvider: function AMI_unregisterProvider(aProvider) { michael@0: if (!aProvider || typeof aProvider != "object") michael@0: throw Components.Exception("aProvider must be specified", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let pos = 0; michael@0: while (pos < this.providers.length) { michael@0: if (this.providers[pos] == aProvider) michael@0: this.providers.splice(pos, 1); michael@0: else michael@0: pos++; michael@0: } michael@0: michael@0: for (let type in this.types) { michael@0: this.types[type].providers = this.types[type].providers.filter(function filterProvider(p) p != aProvider); michael@0: if (this.types[type].providers.length == 0) { michael@0: let oldType = this.types[type].type; michael@0: delete this.types[type]; michael@0: michael@0: let typeListeners = this.typeListeners.slice(0); michael@0: for (let listener of typeListeners) { michael@0: safeCall(function listenerSafeCall() { michael@0: listener.onTypeRemoved(oldType); michael@0: }); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // If we're unregistering after startup call this provider's shutdown. michael@0: if (gStarted) michael@0: callProvider(aProvider, "shutdown"); michael@0: }, michael@0: michael@0: /** michael@0: * Calls a method on all registered providers if it exists and consumes any michael@0: * thrown exception. Return values are ignored. Any parameters after the michael@0: * method parameter are passed to the provider's method. michael@0: * michael@0: * @param aMethod michael@0: * The method name to call michael@0: * @see callProvider michael@0: */ michael@0: callProviders: function AMI_callProviders(aMethod, ...aArgs) { michael@0: if (!aMethod || typeof aMethod != "string") michael@0: throw Components.Exception("aMethod must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let providers = this.providers.slice(0); michael@0: for (let provider of providers) { michael@0: try { michael@0: if (aMethod in provider) michael@0: provider[aMethod].apply(provider, aArgs); michael@0: } michael@0: catch (e) { michael@0: AddonManagerPrivate.recordException("AMI", "provider " + aMethod, e); michael@0: logger.error("Exception calling provider " + aMethod, e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Calls a method on all registered providers, if the provider implements michael@0: * the method. The called method is expected to return a promise, and michael@0: * callProvidersAsync returns a promise that resolves when every provider michael@0: * method has either resolved or rejected. Rejection reasons are logged michael@0: * but otherwise ignored. Return values are ignored. Any parameters after the michael@0: * method parameter are passed to the provider's method. michael@0: * michael@0: * @param aMethod michael@0: * The method name to call michael@0: * @see callProvider michael@0: */ michael@0: callProvidersAsync: function AMI_callProviders(aMethod, ...aArgs) { michael@0: if (!aMethod || typeof aMethod != "string") michael@0: throw Components.Exception("aMethod must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let allProviders = []; michael@0: michael@0: let providers = this.providers.slice(0); michael@0: for (let provider of providers) { michael@0: try { michael@0: if (aMethod in provider) { michael@0: // Resolve a new promise with the result of the method, to handle both michael@0: // methods that return values (or nothing) and methods that return promises. michael@0: let providerResult = provider[aMethod].apply(provider, aArgs); michael@0: let nextPromise = Promise.resolve(providerResult); michael@0: // Log and swallow the errors from methods that do return promises. michael@0: nextPromise = nextPromise.then( michael@0: null, michael@0: e => logger.error("Exception calling provider " + aMethod, e)); michael@0: allProviders.push(nextPromise); michael@0: } michael@0: } michael@0: catch (e) { michael@0: logger.error("Exception calling provider " + aMethod, e); michael@0: } michael@0: } michael@0: // Because we use promise.then to catch and log all errors above, Promise.all() michael@0: // will never exit early because of a rejection. michael@0: return Promise.all(allProviders); michael@0: }, michael@0: michael@0: /** michael@0: * Shuts down the addon manager and all registered providers, this must clean michael@0: * up everything in order for automated tests to fake restarts. michael@0: * @return Promise{null} that resolves when all providers and dependent modules michael@0: * have finished shutting down michael@0: */ michael@0: shutdown: function AMI_shutdown() { michael@0: logger.debug("shutdown"); michael@0: // Clean up listeners michael@0: Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this); michael@0: Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this); michael@0: Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this); michael@0: Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this); michael@0: Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this); michael@0: Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this); michael@0: michael@0: // Only shut down providers if they've been started. Shut down michael@0: // AddonRepository after providers (if any). michael@0: let shuttingDown = null; michael@0: if (gStarted) { michael@0: shuttingDown = this.callProvidersAsync("shutdown") michael@0: .then(null, michael@0: err => logger.error("Failure during async provider shutdown", err)) michael@0: .then(() => AddonRepository.shutdown()); michael@0: } michael@0: else { michael@0: shuttingDown = AddonRepository.shutdown(); michael@0: } michael@0: michael@0: shuttingDown.then(val => logger.debug("Async provider shutdown done"), michael@0: err => logger.error("Failure during AddonRepository shutdown", err)) michael@0: .then(() => { michael@0: this.managerListeners.splice(0, this.managerListeners.length); michael@0: this.installListeners.splice(0, this.installListeners.length); michael@0: this.addonListeners.splice(0, this.addonListeners.length); michael@0: this.typeListeners.splice(0, this.typeListeners.length); michael@0: for (let type in this.startupChanges) michael@0: delete this.startupChanges[type]; michael@0: gStarted = false; michael@0: gStartupComplete = false; michael@0: }); michael@0: return shuttingDown; michael@0: }, michael@0: michael@0: /** michael@0: * Notified when a preference we're interested in has changed. michael@0: * michael@0: * @see nsIObserver michael@0: */ michael@0: observe: function AMI_observe(aSubject, aTopic, aData) { michael@0: switch (aData) { michael@0: case PREF_EM_CHECK_COMPATIBILITY: { michael@0: let oldValue = gCheckCompatibility; michael@0: try { michael@0: gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY); michael@0: } catch(e) { michael@0: gCheckCompatibility = true; michael@0: } michael@0: michael@0: this.callManagerListeners("onCompatibilityModeChanged"); michael@0: michael@0: if (gCheckCompatibility != oldValue) michael@0: this.updateAddonAppDisabledStates(); michael@0: michael@0: break; michael@0: } michael@0: case PREF_EM_STRICT_COMPATIBILITY: { michael@0: let oldValue = gStrictCompatibility; michael@0: try { michael@0: gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY); michael@0: } catch(e) { michael@0: gStrictCompatibility = true; michael@0: } michael@0: michael@0: this.callManagerListeners("onCompatibilityModeChanged"); michael@0: michael@0: if (gStrictCompatibility != oldValue) michael@0: this.updateAddonAppDisabledStates(); michael@0: michael@0: break; michael@0: } michael@0: case PREF_EM_CHECK_UPDATE_SECURITY: { michael@0: let oldValue = gCheckUpdateSecurity; michael@0: try { michael@0: gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY); michael@0: } catch(e) { michael@0: gCheckUpdateSecurity = true; michael@0: } michael@0: michael@0: this.callManagerListeners("onCheckUpdateSecurityChanged"); michael@0: michael@0: if (gCheckUpdateSecurity != oldValue) michael@0: this.updateAddonAppDisabledStates(); michael@0: michael@0: break; michael@0: } michael@0: case PREF_EM_UPDATE_ENABLED: { michael@0: let oldValue = gUpdateEnabled; michael@0: try { michael@0: gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED); michael@0: } catch(e) { michael@0: gUpdateEnabled = true; michael@0: } michael@0: michael@0: this.callManagerListeners("onUpdateModeChanged"); michael@0: break; michael@0: } michael@0: case PREF_EM_AUTOUPDATE_DEFAULT: { michael@0: let oldValue = gAutoUpdateDefault; michael@0: try { michael@0: gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT); michael@0: } catch(e) { michael@0: gAutoUpdateDefault = true; michael@0: } michael@0: michael@0: this.callManagerListeners("onUpdateModeChanged"); michael@0: break; michael@0: } michael@0: case PREF_EM_HOTFIX_ID: { michael@0: try { michael@0: gHotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID); michael@0: } catch(e) { michael@0: gHotfixID = null; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Replaces %...% strings in an addon url (update and updateInfo) with michael@0: * appropriate values. michael@0: * michael@0: * @param aAddon michael@0: * The Addon representing the add-on michael@0: * @param aUri michael@0: * The string representation of the URI to escape michael@0: * @param aAppVersion michael@0: * The optional application version to use for %APP_VERSION% michael@0: * @return The appropriately escaped URI. michael@0: */ michael@0: escapeAddonURI: function AMI_escapeAddonURI(aAddon, aUri, aAppVersion) michael@0: { michael@0: if (!aAddon || typeof aAddon != "object") michael@0: throw Components.Exception("aAddon must be an Addon object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!aUri || typeof aUri != "string") michael@0: throw Components.Exception("aUri must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aAppVersion && typeof aAppVersion != "string") michael@0: throw Components.Exception("aAppVersion must be a string or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled" michael@0: : "userEnabled"; michael@0: michael@0: if (!aAddon.isCompatible) michael@0: addonStatus += ",incompatible"; michael@0: if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) michael@0: addonStatus += ",blocklisted"; michael@0: if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) michael@0: addonStatus += ",softblocked"; michael@0: michael@0: try { michael@0: var xpcomABI = Services.appinfo.XPCOMABI; michael@0: } catch (ex) { michael@0: xpcomABI = UNKNOWN_XPCOM_ABI; michael@0: } michael@0: michael@0: let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id); michael@0: uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version); michael@0: uri = uri.replace(/%ITEM_STATUS%/g, addonStatus); michael@0: uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID); michael@0: uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion : michael@0: Services.appinfo.version); michael@0: uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION); michael@0: uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS); michael@0: uri = uri.replace(/%APP_ABI%/g, xpcomABI); michael@0: uri = uri.replace(/%APP_LOCALE%/g, getLocale()); michael@0: uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version); michael@0: michael@0: // Replace custom parameters (names of custom parameters must have at michael@0: // least 3 characters to prevent lookups for something like %D0%C8) michael@0: var catMan = null; michael@0: uri = uri.replace(/%(\w{3,})%/g, function parameterReplace(aMatch, aParam) { michael@0: if (!catMan) { michael@0: catMan = Cc["@mozilla.org/categorymanager;1"]. michael@0: getService(Ci.nsICategoryManager); michael@0: } michael@0: michael@0: try { michael@0: var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam); michael@0: var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2); michael@0: return paramHandler.getPropertyAsAString(aParam); michael@0: } michael@0: catch(e) { michael@0: return aMatch; michael@0: } michael@0: }); michael@0: michael@0: // escape() does not properly encode + symbols in any embedded FVF strings. michael@0: return uri.replace(/\+/g, "%2B"); michael@0: }, michael@0: michael@0: /** michael@0: * Performs a background update check by starting an update for all add-ons michael@0: * that can be updated. michael@0: */ michael@0: backgroundUpdateCheck: function AMI_backgroundUpdateCheck() { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: let hotfixID = this.hotfixID; michael@0: michael@0: let checkHotfix = hotfixID && michael@0: Services.prefs.getBoolPref(PREF_APP_UPDATE_ENABLED) && michael@0: Services.prefs.getBoolPref(PREF_APP_UPDATE_AUTO); michael@0: michael@0: if (!this.updateEnabled && !checkHotfix) michael@0: return; michael@0: michael@0: Services.obs.notifyObservers(null, "addons-background-update-start", null); michael@0: michael@0: // Start this from one to ensure the whole of this function completes before michael@0: // we can send the complete notification. Some parts can in some cases michael@0: // complete synchronously before later parts have a chance to increment michael@0: // pendingUpdates. michael@0: let pendingUpdates = 1; michael@0: michael@0: function notifyComplete() { michael@0: if (--pendingUpdates == 0) { michael@0: Services.obs.notifyObservers(null, michael@0: "addons-background-update-complete", michael@0: null); michael@0: } michael@0: } michael@0: michael@0: if (this.updateEnabled) { michael@0: let scope = {}; michael@0: Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope); michael@0: scope.LightweightThemeManager.updateCurrentTheme(); michael@0: michael@0: pendingUpdates++; michael@0: this.getAllAddons(function getAddonsCallback(aAddons) { michael@0: // If there is a known hotfix then exclude it from the list of add-ons to update. michael@0: var ids = [a.id for each (a in aAddons) if (a.id != hotfixID)]; michael@0: michael@0: // Repopulate repository cache first, to ensure compatibility overrides michael@0: // are up to date before checking for addon updates. michael@0: AddonRepository.backgroundUpdateCheck( michael@0: ids, function BUC_backgroundUpdateCheckCallback() { michael@0: pendingUpdates += aAddons.length; michael@0: aAddons.forEach(function BUC_forEachCallback(aAddon) { michael@0: if (aAddon.id == hotfixID) { michael@0: notifyComplete(); michael@0: return; michael@0: } michael@0: michael@0: // Check all add-ons for updates so that any compatibility updates will michael@0: // be applied michael@0: aAddon.findUpdates({ michael@0: onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) { michael@0: // Start installing updates when the add-on can be updated and michael@0: // background updates should be applied. michael@0: if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && michael@0: AddonManager.shouldAutoUpdate(aAddon)) { michael@0: aInstall.install(); michael@0: } michael@0: }, michael@0: michael@0: onUpdateFinished: notifyComplete michael@0: }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); michael@0: }); michael@0: michael@0: notifyComplete(); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: if (checkHotfix) { michael@0: var hotfixVersion = ""; michael@0: try { michael@0: hotfixVersion = Services.prefs.getCharPref(PREF_EM_HOTFIX_LASTVERSION); michael@0: } michael@0: catch (e) { } michael@0: michael@0: let url = null; michael@0: if (Services.prefs.getPrefType(PREF_EM_HOTFIX_URL) == Ci.nsIPrefBranch.PREF_STRING) michael@0: url = Services.prefs.getCharPref(PREF_EM_HOTFIX_URL); michael@0: else michael@0: url = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL); michael@0: michael@0: // Build the URI from a fake add-on data. michael@0: url = AddonManager.escapeAddonURI({ michael@0: id: hotfixID, michael@0: version: hotfixVersion, michael@0: userDisabled: false, michael@0: appDisabled: false michael@0: }, url); michael@0: michael@0: pendingUpdates++; michael@0: Components.utils.import("resource://gre/modules/addons/AddonUpdateChecker.jsm"); michael@0: AddonUpdateChecker.checkForUpdates(hotfixID, null, url, { michael@0: onUpdateCheckComplete: function BUC_onUpdateCheckComplete(aUpdates) { michael@0: let update = AddonUpdateChecker.getNewestCompatibleUpdate(aUpdates); michael@0: if (!update) { michael@0: notifyComplete(); michael@0: return; michael@0: } michael@0: michael@0: // If the available version isn't newer than the last installed michael@0: // version then ignore it. michael@0: if (Services.vc.compare(hotfixVersion, update.version) >= 0) { michael@0: notifyComplete(); michael@0: return; michael@0: } michael@0: michael@0: logger.debug("Downloading hotfix version " + update.version); michael@0: AddonManager.getInstallForURL(update.updateURL, michael@0: function BUC_getInstallForURL(aInstall) { michael@0: aInstall.addListener({ michael@0: onDownloadEnded: function BUC_onDownloadEnded(aInstall) { michael@0: try { michael@0: if (!Services.prefs.getBoolPref(PREF_EM_CERT_CHECKATTRIBUTES)) michael@0: return; michael@0: } michael@0: catch (e) { michael@0: // By default don't do certificate checks. michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: CertUtils.validateCert(aInstall.certificate, michael@0: CertUtils.readCertPrefs(PREF_EM_HOTFIX_CERTS)); michael@0: } michael@0: catch (e) { michael@0: logger.warn("The hotfix add-on was not signed by the expected " + michael@0: "certificate and so will not be installed."); michael@0: aInstall.cancel(); michael@0: } michael@0: }, michael@0: michael@0: onInstallEnded: function BUC_onInstallEnded(aInstall) { michael@0: // Remember the last successfully installed version. michael@0: Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, michael@0: aInstall.version); michael@0: }, michael@0: michael@0: onInstallCancelled: function BUC_onInstallCancelled(aInstall) { michael@0: // Revert to the previous version if the installation was michael@0: // cancelled. michael@0: Services.prefs.setCharPref(PREF_EM_HOTFIX_LASTVERSION, michael@0: hotfixVersion); michael@0: } michael@0: }); michael@0: michael@0: aInstall.install(); michael@0: michael@0: notifyComplete(); michael@0: }, "application/x-xpinstall", update.updateHash, null, michael@0: null, update.version); michael@0: }, michael@0: michael@0: onUpdateCheckError: notifyComplete michael@0: }); michael@0: } michael@0: michael@0: notifyComplete(); michael@0: }, michael@0: michael@0: /** michael@0: * Adds a add-on to the list of detected changes for this startup. If michael@0: * addStartupChange is called multiple times for the same add-on in the same michael@0: * startup then only the most recent change will be remembered. michael@0: * michael@0: * @param aType michael@0: * The type of change as a string. Providers can define their own michael@0: * types of changes or use the existing defined STARTUP_CHANGE_* michael@0: * constants michael@0: * @param aID michael@0: * The ID of the add-on michael@0: */ michael@0: addStartupChange: function AMI_addStartupChange(aType, aID) { michael@0: if (!aType || typeof aType != "string") michael@0: throw Components.Exception("aType must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!aID || typeof aID != "string") michael@0: throw Components.Exception("aID must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (gStartupComplete) michael@0: return; michael@0: michael@0: // Ensure that an ID is only listed in one type of change michael@0: for (let type in this.startupChanges) michael@0: this.removeStartupChange(type, aID); michael@0: michael@0: if (!(aType in this.startupChanges)) michael@0: this.startupChanges[aType] = []; michael@0: this.startupChanges[aType].push(aID); michael@0: }, michael@0: michael@0: /** michael@0: * Removes a startup change for an add-on. michael@0: * michael@0: * @param aType michael@0: * The type of change michael@0: * @param aID michael@0: * The ID of the add-on michael@0: */ michael@0: removeStartupChange: function AMI_removeStartupChange(aType, aID) { michael@0: if (!aType || typeof aType != "string") michael@0: throw Components.Exception("aType must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!aID || typeof aID != "string") michael@0: throw Components.Exception("aID must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (gStartupComplete) michael@0: return; michael@0: michael@0: if (!(aType in this.startupChanges)) michael@0: return; michael@0: michael@0: this.startupChanges[aType] = this.startupChanges[aType].filter( michael@0: function filterItem(aItem) aItem != aID); michael@0: }, michael@0: michael@0: /** michael@0: * Calls all registered AddonManagerListeners with an event. Any parameters michael@0: * after the method parameter are passed to the listener. michael@0: * michael@0: * @param aMethod michael@0: * The method on the listeners to call michael@0: */ michael@0: callManagerListeners: function AMI_callManagerListeners(aMethod, ...aArgs) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aMethod || typeof aMethod != "string") michael@0: throw Components.Exception("aMethod must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let managerListeners = this.managerListeners.slice(0); michael@0: for (let listener of managerListeners) { michael@0: try { michael@0: if (aMethod in listener) michael@0: listener[aMethod].apply(listener, aArgs); michael@0: } michael@0: catch (e) { michael@0: logger.warn("AddonManagerListener threw exception when calling " + aMethod, e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Calls all registered InstallListeners with an event. Any parameters after michael@0: * the extraListeners parameter are passed to the listener. michael@0: * michael@0: * @param aMethod michael@0: * The method on the listeners to call michael@0: * @param aExtraListeners michael@0: * An optional array of extra InstallListeners to also call michael@0: * @return false if any of the listeners returned false, true otherwise michael@0: */ michael@0: callInstallListeners: function AMI_callInstallListeners(aMethod, michael@0: aExtraListeners, ...aArgs) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aMethod || typeof aMethod != "string") michael@0: throw Components.Exception("aMethod must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aExtraListeners && !Array.isArray(aExtraListeners)) michael@0: throw Components.Exception("aExtraListeners must be an array or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let result = true; michael@0: let listeners; michael@0: if (aExtraListeners) michael@0: listeners = aExtraListeners.concat(this.installListeners); michael@0: else michael@0: listeners = this.installListeners.slice(0); michael@0: michael@0: for (let listener of listeners) { michael@0: try { michael@0: if (aMethod in listener) { michael@0: if (listener[aMethod].apply(listener, aArgs) === false) michael@0: result = false; michael@0: } michael@0: } michael@0: catch (e) { michael@0: logger.warn("InstallListener threw exception when calling " + aMethod, e); michael@0: } michael@0: } michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Calls all registered AddonListeners with an event. Any parameters after michael@0: * the method parameter are passed to the listener. michael@0: * michael@0: * @param aMethod michael@0: * The method on the listeners to call michael@0: */ michael@0: callAddonListeners: function AMI_callAddonListeners(aMethod, ...aArgs) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aMethod || typeof aMethod != "string") michael@0: throw Components.Exception("aMethod must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let addonListeners = this.addonListeners.slice(0); michael@0: for (let listener of addonListeners) { michael@0: try { michael@0: if (aMethod in listener) michael@0: listener[aMethod].apply(listener, aArgs); michael@0: } michael@0: catch (e) { michael@0: logger.warn("AddonListener threw exception when calling " + aMethod, e); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Notifies all providers that an add-on has been enabled when that type of michael@0: * add-on only supports a single add-on being enabled at a time. This allows michael@0: * the providers to disable theirs if necessary. michael@0: * michael@0: * @param aID michael@0: * The ID of the enabled add-on michael@0: * @param aType michael@0: * The type of the enabled add-on michael@0: * @param aPendingRestart michael@0: * A boolean indicating if the change will only take place the next michael@0: * time the application is restarted michael@0: */ michael@0: notifyAddonChanged: function AMI_notifyAddonChanged(aID, aType, aPendingRestart) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (aID && typeof aID != "string") michael@0: throw Components.Exception("aID must be a string or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!aType || typeof aType != "string") michael@0: throw Components.Exception("aType must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: this.callProviders("addonChanged", aID, aType, aPendingRestart); michael@0: }, michael@0: michael@0: /** michael@0: * Notifies all providers they need to update the appDisabled property for michael@0: * their add-ons in response to an application change such as a blocklist michael@0: * update. michael@0: */ michael@0: updateAddonAppDisabledStates: function AMI_updateAddonAppDisabledStates() { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: this.callProviders("updateAddonAppDisabledStates"); michael@0: }, michael@0: michael@0: /** michael@0: * Notifies all providers that the repository has updated its data for michael@0: * installed add-ons. michael@0: * michael@0: * @param aCallback michael@0: * Function to call when operation is complete. michael@0: */ michael@0: updateAddonRepositoryData: function AMI_updateAddonRepositoryData(aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", { michael@0: nextObject: function updateAddonRepositoryData_nextObject(aCaller, aProvider) { michael@0: callProvider(aProvider, michael@0: "updateAddonRepositoryData", michael@0: null, michael@0: aCaller.callNext.bind(aCaller)); michael@0: }, michael@0: noMoreObjects: function updateAddonRepositoryData_noMoreObjects(aCaller) { michael@0: safeCall(aCallback); michael@0: // only tests should care about this michael@0: Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets an AddonInstall for a URL. michael@0: * michael@0: * @param aUrl michael@0: * The string represenation of the URL the add-on is located at michael@0: * @param aCallback michael@0: * A callback to pass the AddonInstall to michael@0: * @param aMimetype michael@0: * The mimetype of the add-on michael@0: * @param aHash michael@0: * An optional hash of the add-on michael@0: * @param aName michael@0: * An optional placeholder name while the add-on is being downloaded michael@0: * @param aIcons michael@0: * Optional placeholder icons while the add-on is being downloaded michael@0: * @param aVersion michael@0: * An optional placeholder version while the add-on is being downloaded michael@0: * @param aLoadGroup michael@0: * An optional nsILoadGroup to associate any network requests with michael@0: * @throws if the aUrl, aCallback or aMimetype arguments are not specified michael@0: */ michael@0: getInstallForURL: function AMI_getInstallForURL(aUrl, aCallback, aMimetype, michael@0: aHash, aName, aIcons, michael@0: aVersion, aLoadGroup) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aUrl || typeof aUrl != "string") michael@0: throw Components.Exception("aURL must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!aMimetype || typeof aMimetype != "string") michael@0: throw Components.Exception("aMimetype must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aHash && typeof aHash != "string") michael@0: throw Components.Exception("aHash must be a string or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aName && typeof aName != "string") michael@0: throw Components.Exception("aName must be a string or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aIcons) { michael@0: if (typeof aIcons == "string") michael@0: aIcons = { "32": aIcons }; michael@0: else if (typeof aIcons != "object") michael@0: throw Components.Exception("aIcons must be a string, an object or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: } else { michael@0: aIcons = {}; michael@0: } michael@0: michael@0: if (aVersion && typeof aVersion != "string") michael@0: throw Components.Exception("aVersion must be a string or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aLoadGroup && (!(aLoadGroup instanceof Ci.nsILoadGroup))) michael@0: throw Components.Exception("aLoadGroup must be a nsILoadGroup or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let providers = this.providers.slice(0); michael@0: for (let provider of providers) { michael@0: if (callProvider(provider, "supportsMimetype", false, aMimetype)) { michael@0: callProvider(provider, "getInstallForURL", null, michael@0: aUrl, aHash, aName, aIcons, aVersion, aLoadGroup, michael@0: function getInstallForURL_safeCall(aInstall) { michael@0: safeCall(aCallback, aInstall); michael@0: }); michael@0: return; michael@0: } michael@0: } michael@0: safeCall(aCallback, null); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets an AddonInstall for an nsIFile. michael@0: * michael@0: * @param aFile michael@0: * The nsIFile where the add-on is located michael@0: * @param aCallback michael@0: * A callback to pass the AddonInstall to michael@0: * @param aMimetype michael@0: * An optional mimetype hint for the add-on michael@0: * @throws if the aFile or aCallback arguments are not specified michael@0: */ michael@0: getInstallForFile: function AMI_getInstallForFile(aFile, aCallback, aMimetype) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!(aFile instanceof Ci.nsIFile)) michael@0: throw Components.Exception("aFile must be a nsIFile", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aMimetype && typeof aMimetype != "string") michael@0: throw Components.Exception("aMimetype must be a string or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: new AsyncObjectCaller(this.providers, "getInstallForFile", { michael@0: nextObject: function getInstallForFile_nextObject(aCaller, aProvider) { michael@0: callProvider(aProvider, "getInstallForFile", null, aFile, michael@0: function getInstallForFile_safeCall(aInstall) { michael@0: if (aInstall) michael@0: safeCall(aCallback, aInstall); michael@0: else michael@0: aCaller.callNext(); michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getInstallForFile_noMoreObjects(aCaller) { michael@0: safeCall(aCallback, null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets all current AddonInstalls optionally limiting to a list michael@0: * of types. michael@0: * michael@0: * @param aTypes michael@0: * An optional array of types to retrieve. Each type is a string name michael@0: * @param aCallback michael@0: * A callback which will be passed an array of AddonInstalls michael@0: * @throws If the aCallback argument is not specified michael@0: */ michael@0: getInstallsByTypes: function AMI_getInstallsByTypes(aTypes, aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (aTypes && !Array.isArray(aTypes)) michael@0: throw Components.Exception("aTypes must be an array or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let installs = []; michael@0: michael@0: new AsyncObjectCaller(this.providers, "getInstallsByTypes", { michael@0: nextObject: function getInstallsByTypes_nextObject(aCaller, aProvider) { michael@0: callProvider(aProvider, "getInstallsByTypes", null, aTypes, michael@0: function getInstallsByTypes_safeCall(aProviderInstalls) { michael@0: installs = installs.concat(aProviderInstalls); michael@0: aCaller.callNext(); michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getInstallsByTypes_noMoreObjects(aCaller) { michael@0: safeCall(aCallback, installs); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets all current AddonInstalls. michael@0: * michael@0: * @param aCallback michael@0: * A callback which will be passed an array of AddonInstalls michael@0: */ michael@0: getAllInstalls: function AMI_getAllInstalls(aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: this.getInstallsByTypes(null, aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * Synchronously map a URI to the corresponding Addon ID. michael@0: * michael@0: * Mappable URIs are limited to in-application resources belonging to the michael@0: * add-on, such as Javascript compartments, XUL windows, XBL bindings, etc. michael@0: * but do not include URIs from meta data, such as the add-on homepage. michael@0: * michael@0: * @param aURI michael@0: * nsIURI to map to an addon id michael@0: * @return string containing the Addon ID or null michael@0: * @see amIAddonManager.mapURIToAddonID michael@0: */ michael@0: mapURIToAddonID: function AMI_mapURIToAddonID(aURI) { michael@0: if (!(aURI instanceof Ci.nsIURI)) { michael@0: throw Components.Exception("aURI is not a nsIURI", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: } michael@0: // Try all providers michael@0: let providers = this.providers.slice(0); michael@0: for (let provider of providers) { michael@0: var id = callProvider(provider, "mapURIToAddonID", null, aURI); michael@0: if (id !== null) { michael@0: return id; michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: /** michael@0: * Checks whether installation is enabled for a particular mimetype. michael@0: * michael@0: * @param aMimetype michael@0: * The mimetype to check michael@0: * @return true if installation is enabled for the mimetype michael@0: */ michael@0: isInstallEnabled: function AMI_isInstallEnabled(aMimetype) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aMimetype || typeof aMimetype != "string") michael@0: throw Components.Exception("aMimetype must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let providers = this.providers.slice(0); michael@0: for (let provider of providers) { michael@0: if (callProvider(provider, "supportsMimetype", false, aMimetype) && michael@0: callProvider(provider, "isInstallEnabled")) michael@0: return true; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Checks whether a particular source is allowed to install add-ons of a michael@0: * given mimetype. michael@0: * michael@0: * @param aMimetype michael@0: * The mimetype of the add-on michael@0: * @param aURI michael@0: * The optional nsIURI of the source michael@0: * @return true if the source is allowed to install this mimetype michael@0: */ michael@0: isInstallAllowed: function AMI_isInstallAllowed(aMimetype, aURI) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aMimetype || typeof aMimetype != "string") michael@0: throw Components.Exception("aMimetype must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aURI && !(aURI instanceof Ci.nsIURI)) michael@0: throw Components.Exception("aURI must be a nsIURI or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let providers = this.providers.slice(0); michael@0: for (let provider of providers) { michael@0: if (callProvider(provider, "supportsMimetype", false, aMimetype) && michael@0: callProvider(provider, "isInstallAllowed", null, aURI)) michael@0: return true; michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Starts installation of an array of AddonInstalls notifying the registered michael@0: * web install listener of blocked or started installs. michael@0: * michael@0: * @param aMimetype michael@0: * The mimetype of add-ons being installed michael@0: * @param aSource michael@0: * The optional nsIDOMWindow that started the installs michael@0: * @param aURI michael@0: * The optional nsIURI that started the installs michael@0: * @param aInstalls michael@0: * The array of AddonInstalls to be installed michael@0: */ michael@0: installAddonsFromWebpage: function AMI_installAddonsFromWebpage(aMimetype, michael@0: aSource, michael@0: aURI, michael@0: aInstalls) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aMimetype || typeof aMimetype != "string") michael@0: throw Components.Exception("aMimetype must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aSource && !(aSource instanceof Ci.nsIDOMWindow)) michael@0: throw Components.Exception("aSource must be a nsIDOMWindow or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (aURI && !(aURI instanceof Ci.nsIURI)) michael@0: throw Components.Exception("aURI must be a nsIURI or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!Array.isArray(aInstalls)) michael@0: throw Components.Exception("aInstalls must be an array", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) { michael@0: logger.warn("No web installer available, cancelling all installs"); michael@0: aInstalls.forEach(function(aInstall) { michael@0: aInstall.cancel(); michael@0: }); michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"]. michael@0: getService(Ci.amIWebInstallListener); michael@0: michael@0: if (!this.isInstallEnabled(aMimetype, aURI)) { michael@0: weblistener.onWebInstallDisabled(aSource, aURI, aInstalls, michael@0: aInstalls.length); michael@0: } michael@0: else if (!this.isInstallAllowed(aMimetype, aURI)) { michael@0: if (weblistener.onWebInstallBlocked(aSource, aURI, aInstalls, michael@0: aInstalls.length)) { michael@0: aInstalls.forEach(function(aInstall) { michael@0: aInstall.install(); michael@0: }); michael@0: } michael@0: } michael@0: else if (weblistener.onWebInstallRequested(aSource, aURI, aInstalls, michael@0: aInstalls.length)) { michael@0: aInstalls.forEach(function(aInstall) { michael@0: aInstall.install(); michael@0: }); michael@0: } michael@0: } michael@0: catch (e) { michael@0: // In the event that the weblistener throws during instantiation or when michael@0: // calling onWebInstallBlocked or onWebInstallRequested all of the michael@0: // installs should get cancelled. michael@0: logger.warn("Failure calling web installer", e); michael@0: aInstalls.forEach(function(aInstall) { michael@0: aInstall.cancel(); michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Adds a new InstallListener if the listener is not already registered. michael@0: * michael@0: * @param aListener michael@0: * The InstallListener to add michael@0: */ michael@0: addInstallListener: function AMI_addInstallListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be a InstallListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!this.installListeners.some(function addInstallListener_matchListener(i) { michael@0: return i == aListener; })) michael@0: this.installListeners.push(aListener); michael@0: }, michael@0: michael@0: /** michael@0: * Removes an InstallListener if the listener is registered. michael@0: * michael@0: * @param aListener michael@0: * The InstallListener to remove michael@0: */ michael@0: removeInstallListener: function AMI_removeInstallListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be a InstallListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let pos = 0; michael@0: while (pos < this.installListeners.length) { michael@0: if (this.installListeners[pos] == aListener) michael@0: this.installListeners.splice(pos, 1); michael@0: else michael@0: pos++; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets an add-on with a specific ID. michael@0: * michael@0: * @param aID michael@0: * The ID of the add-on to retrieve michael@0: * @param aCallback michael@0: * The callback to pass the retrieved add-on to michael@0: * @throws if the aID or aCallback arguments are not specified michael@0: */ michael@0: getAddonByID: function AMI_getAddonByID(aID, aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aID || typeof aID != "string") michael@0: throw Components.Exception("aID must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: new AsyncObjectCaller(this.providers, "getAddonByID", { michael@0: nextObject: function getAddonByID_nextObject(aCaller, aProvider) { michael@0: callProvider(aProvider, "getAddonByID", null, aID, michael@0: function getAddonByID_safeCall(aAddon) { michael@0: if (aAddon) michael@0: safeCall(aCallback, aAddon); michael@0: else michael@0: aCaller.callNext(); michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getAddonByID_noMoreObjects(aCaller) { michael@0: safeCall(aCallback, null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously get an add-on with a specific Sync GUID. michael@0: * michael@0: * @param aGUID michael@0: * String GUID of add-on to retrieve michael@0: * @param aCallback michael@0: * The callback to pass the retrieved add-on to. michael@0: * @throws if the aGUID or aCallback arguments are not specified michael@0: */ michael@0: getAddonBySyncGUID: function AMI_getAddonBySyncGUID(aGUID, aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!aGUID || typeof aGUID != "string") michael@0: throw Components.Exception("aGUID must be a non-empty string", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", { michael@0: nextObject: function getAddonBySyncGUID_nextObject(aCaller, aProvider) { michael@0: callProvider(aProvider, "getAddonBySyncGUID", null, aGUID, michael@0: function getAddonBySyncGUID_safeCall(aAddon) { michael@0: if (aAddon) { michael@0: safeCall(aCallback, aAddon); michael@0: } else { michael@0: aCaller.callNext(); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getAddonBySyncGUID_noMoreObjects(aCaller) { michael@0: safeCall(aCallback, null); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets an array of add-ons. michael@0: * michael@0: * @param aIDs michael@0: * The array of IDs to retrieve michael@0: * @param aCallback michael@0: * The callback to pass an array of Addons to michael@0: * @throws if the aID or aCallback arguments are not specified michael@0: */ michael@0: getAddonsByIDs: function AMI_getAddonsByIDs(aIDs, aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (!Array.isArray(aIDs)) michael@0: throw Components.Exception("aIDs must be an array", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let addons = []; michael@0: michael@0: new AsyncObjectCaller(aIDs, null, { michael@0: nextObject: function getAddonsByIDs_nextObject(aCaller, aID) { michael@0: AddonManagerInternal.getAddonByID(aID, michael@0: function getAddonsByIDs_getAddonByID(aAddon) { michael@0: addons.push(aAddon); michael@0: aCaller.callNext(); michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getAddonsByIDs_noMoreObjects(aCaller) { michael@0: safeCall(aCallback, addons); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets add-ons of specific types. michael@0: * michael@0: * @param aTypes michael@0: * An optional array of types to retrieve. Each type is a string name michael@0: * @param aCallback michael@0: * The callback to pass an array of Addons to. michael@0: * @throws if the aCallback argument is not specified michael@0: */ michael@0: getAddonsByTypes: function AMI_getAddonsByTypes(aTypes, aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (aTypes && !Array.isArray(aTypes)) michael@0: throw Components.Exception("aTypes must be an array or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let addons = []; michael@0: michael@0: new AsyncObjectCaller(this.providers, "getAddonsByTypes", { michael@0: nextObject: function getAddonsByTypes_nextObject(aCaller, aProvider) { michael@0: callProvider(aProvider, "getAddonsByTypes", null, aTypes, michael@0: function getAddonsByTypes_concatAddons(aProviderAddons) { michael@0: addons = addons.concat(aProviderAddons); michael@0: aCaller.callNext(); michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getAddonsByTypes_noMoreObjects(aCaller) { michael@0: safeCall(aCallback, addons); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets all installed add-ons. michael@0: * michael@0: * @param aCallback michael@0: * A callback which will be passed an array of Addons michael@0: */ michael@0: getAllAddons: function AMI_getAllAddons(aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: this.getAddonsByTypes(null, aCallback); michael@0: }, michael@0: michael@0: /** michael@0: * Asynchronously gets add-ons that have operations waiting for an application michael@0: * restart to complete. michael@0: * michael@0: * @param aTypes michael@0: * An optional array of types to retrieve. Each type is a string name michael@0: * @param aCallback michael@0: * The callback to pass the array of Addons to michael@0: * @throws if the aCallback argument is not specified michael@0: */ michael@0: getAddonsWithOperationsByTypes: michael@0: function AMI_getAddonsWithOperationsByTypes(aTypes, aCallback) { michael@0: if (!gStarted) michael@0: throw Components.Exception("AddonManager is not initialized", michael@0: Cr.NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: if (aTypes && !Array.isArray(aTypes)) michael@0: throw Components.Exception("aTypes must be an array or null", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (typeof aCallback != "function") michael@0: throw Components.Exception("aCallback must be a function", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let addons = []; michael@0: michael@0: new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", { michael@0: nextObject: function getAddonsWithOperationsByTypes_nextObject michael@0: (aCaller, aProvider) { michael@0: callProvider(aProvider, "getAddonsWithOperationsByTypes", null, aTypes, michael@0: function getAddonsWithOperationsByTypes_concatAddons michael@0: (aProviderAddons) { michael@0: addons = addons.concat(aProviderAddons); michael@0: aCaller.callNext(); michael@0: }); michael@0: }, michael@0: michael@0: noMoreObjects: function getAddonsWithOperationsByTypes_noMoreObjects(caller) { michael@0: safeCall(aCallback, addons); michael@0: } michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Adds a new AddonManagerListener if the listener is not already registered. michael@0: * michael@0: * @param aListener michael@0: * The listener to add michael@0: */ michael@0: addManagerListener: function AMI_addManagerListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be an AddonManagerListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!this.managerListeners.some(function addManagerListener_matchListener(i) { michael@0: return i == aListener; })) michael@0: this.managerListeners.push(aListener); michael@0: }, michael@0: michael@0: /** michael@0: * Removes an AddonManagerListener if the listener is registered. michael@0: * michael@0: * @param aListener michael@0: * The listener to remove michael@0: */ michael@0: removeManagerListener: function AMI_removeManagerListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be an AddonManagerListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let pos = 0; michael@0: while (pos < this.managerListeners.length) { michael@0: if (this.managerListeners[pos] == aListener) michael@0: this.managerListeners.splice(pos, 1); michael@0: else michael@0: pos++; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Adds a new AddonListener if the listener is not already registered. michael@0: * michael@0: * @param aListener michael@0: * The AddonListener to add michael@0: */ michael@0: addAddonListener: function AMI_addAddonListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be an AddonListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!this.addonListeners.some(function addAddonListener_matchListener(i) { michael@0: return i == aListener; })) michael@0: this.addonListeners.push(aListener); michael@0: }, michael@0: michael@0: /** michael@0: * Removes an AddonListener if the listener is registered. michael@0: * michael@0: * @param aListener michael@0: * The AddonListener to remove michael@0: */ michael@0: removeAddonListener: function AMI_removeAddonListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be an AddonListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let pos = 0; michael@0: while (pos < this.addonListeners.length) { michael@0: if (this.addonListeners[pos] == aListener) michael@0: this.addonListeners.splice(pos, 1); michael@0: else michael@0: pos++; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Adds a new TypeListener if the listener is not already registered. michael@0: * michael@0: * @param aListener michael@0: * The TypeListener to add michael@0: */ michael@0: addTypeListener: function AMI_addTypeListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be a TypeListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!this.typeListeners.some(function addTypeListener_matchListener(i) { michael@0: return i == aListener; })) michael@0: this.typeListeners.push(aListener); michael@0: }, michael@0: michael@0: /** michael@0: * Removes an TypeListener if the listener is registered. michael@0: * michael@0: * @param aListener michael@0: * The TypeListener to remove michael@0: */ michael@0: removeTypeListener: function AMI_removeTypeListener(aListener) { michael@0: if (!aListener || typeof aListener != "object") michael@0: throw Components.Exception("aListener must be a TypeListener object", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: let pos = 0; michael@0: while (pos < this.typeListeners.length) { michael@0: if (this.typeListeners[pos] == aListener) michael@0: this.typeListeners.splice(pos, 1); michael@0: else michael@0: pos++; michael@0: } michael@0: }, michael@0: michael@0: get addonTypes() { michael@0: return this.typesProxy; michael@0: }, michael@0: michael@0: get autoUpdateDefault() { michael@0: return gAutoUpdateDefault; michael@0: }, michael@0: michael@0: set autoUpdateDefault(aValue) { michael@0: aValue = !!aValue; michael@0: if (aValue != gAutoUpdateDefault) michael@0: Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue); michael@0: return aValue; michael@0: }, michael@0: michael@0: get checkCompatibility() { michael@0: return gCheckCompatibility; michael@0: }, michael@0: michael@0: set checkCompatibility(aValue) { michael@0: aValue = !!aValue; michael@0: if (aValue != gCheckCompatibility) { michael@0: if (!aValue) michael@0: Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false); michael@0: else michael@0: Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY); michael@0: } michael@0: return aValue; michael@0: }, michael@0: michael@0: get strictCompatibility() { michael@0: return gStrictCompatibility; michael@0: }, michael@0: michael@0: set strictCompatibility(aValue) { michael@0: aValue = !!aValue; michael@0: if (aValue != gStrictCompatibility) michael@0: Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue); michael@0: return aValue; michael@0: }, michael@0: michael@0: get checkUpdateSecurityDefault() { michael@0: return gCheckUpdateSecurityDefault; michael@0: }, michael@0: michael@0: get checkUpdateSecurity() { michael@0: return gCheckUpdateSecurity; michael@0: }, michael@0: michael@0: set checkUpdateSecurity(aValue) { michael@0: aValue = !!aValue; michael@0: if (aValue != gCheckUpdateSecurity) { michael@0: if (aValue != gCheckUpdateSecurityDefault) michael@0: Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue); michael@0: else michael@0: Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); michael@0: } michael@0: return aValue; michael@0: }, michael@0: michael@0: get updateEnabled() { michael@0: return gUpdateEnabled; michael@0: }, michael@0: michael@0: set updateEnabled(aValue) { michael@0: aValue = !!aValue; michael@0: if (aValue != gUpdateEnabled) michael@0: Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue); michael@0: return aValue; michael@0: }, michael@0: michael@0: get hotfixID() { michael@0: return gHotfixID; michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * Should not be used outside of core Mozilla code. This is a private API for michael@0: * the startup and platform integration code to use. Refer to the methods on michael@0: * AddonManagerInternal for documentation however note that these methods are michael@0: * subject to change at any time. michael@0: */ michael@0: this.AddonManagerPrivate = { michael@0: startup: function AMP_startup() { michael@0: AddonManagerInternal.startup(); michael@0: }, michael@0: michael@0: registerProvider: function AMP_registerProvider(aProvider, aTypes) { michael@0: AddonManagerInternal.registerProvider(aProvider, aTypes); michael@0: }, michael@0: michael@0: unregisterProvider: function AMP_unregisterProvider(aProvider) { michael@0: AddonManagerInternal.unregisterProvider(aProvider); michael@0: }, michael@0: michael@0: backgroundUpdateCheck: function AMP_backgroundUpdateCheck() { michael@0: AddonManagerInternal.backgroundUpdateCheck(); michael@0: }, michael@0: michael@0: addStartupChange: function AMP_addStartupChange(aType, aID) { michael@0: AddonManagerInternal.addStartupChange(aType, aID); michael@0: }, michael@0: michael@0: removeStartupChange: function AMP_removeStartupChange(aType, aID) { michael@0: AddonManagerInternal.removeStartupChange(aType, aID); michael@0: }, michael@0: michael@0: notifyAddonChanged: function AMP_notifyAddonChanged(aID, aType, aPendingRestart) { michael@0: AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart); michael@0: }, michael@0: michael@0: updateAddonAppDisabledStates: function AMP_updateAddonAppDisabledStates() { michael@0: AddonManagerInternal.updateAddonAppDisabledStates(); michael@0: }, michael@0: michael@0: updateAddonRepositoryData: function AMP_updateAddonRepositoryData(aCallback) { michael@0: AddonManagerInternal.updateAddonRepositoryData(aCallback); michael@0: }, michael@0: michael@0: callInstallListeners: function AMP_callInstallListeners(...aArgs) { michael@0: return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal, michael@0: aArgs); michael@0: }, michael@0: michael@0: callAddonListeners: function AMP_callAddonListeners(...aArgs) { michael@0: AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs); michael@0: }, michael@0: michael@0: AddonAuthor: AddonAuthor, michael@0: michael@0: AddonScreenshot: AddonScreenshot, michael@0: michael@0: AddonCompatibilityOverride: AddonCompatibilityOverride, michael@0: michael@0: AddonType: AddonType, michael@0: michael@0: recordTimestamp: function AMP_recordTimestamp(name, value) { michael@0: AddonManagerInternal.recordTimestamp(name, value); michael@0: }, michael@0: michael@0: _simpleMeasures: {}, michael@0: recordSimpleMeasure: function AMP_recordSimpleMeasure(name, value) { michael@0: this._simpleMeasures[name] = value; michael@0: }, michael@0: michael@0: recordException: function AMP_recordException(aModule, aContext, aException) { michael@0: let report = { michael@0: module: aModule, michael@0: context: aContext michael@0: }; michael@0: michael@0: if (typeof aException == "number") { michael@0: report.message = Components.Exception("", aException).name; michael@0: } michael@0: else { michael@0: report.message = aException.toString(); michael@0: if (aException.fileName) { michael@0: report.file = aException.fileName; michael@0: report.line = aException.lineNumber; michael@0: } michael@0: } michael@0: michael@0: this._simpleMeasures.exception = report; michael@0: }, michael@0: michael@0: getSimpleMeasures: function AMP_getSimpleMeasures() { michael@0: return this._simpleMeasures; michael@0: }, michael@0: michael@0: getTelemetryDetails: function AMP_getTelemetryDetails() { michael@0: return AddonManagerInternal.telemetryDetails; michael@0: }, michael@0: michael@0: setTelemetryDetails: function AMP_setTelemetryDetails(aProvider, aDetails) { michael@0: AddonManagerInternal.telemetryDetails[aProvider] = aDetails; michael@0: }, michael@0: michael@0: // Start a timer, record a simple measure of the time interval when michael@0: // timer.done() is called michael@0: simpleTimer: function(aName) { michael@0: let startTime = Date.now(); michael@0: return { michael@0: done: () => this.recordSimpleMeasure(aName, Date.now() - startTime) michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Helper to call update listeners when no update is available. michael@0: * michael@0: * This can be used as an implementation for Addon.findUpdates() when michael@0: * no update mechanism is available. michael@0: */ michael@0: callNoUpdateListeners: function (addon, listener, reason, appVersion, platformVersion) { michael@0: if ("onNoCompatibilityUpdateAvailable" in listener) { michael@0: safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon); michael@0: } michael@0: if ("onNoUpdateAvailable" in listener) { michael@0: safeCall(listener.onNoUpdateAvailable.bind(listener), addon); michael@0: } michael@0: if ("onUpdateFinished" in listener) { michael@0: safeCall(listener.onUpdateFinished.bind(listener), addon); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: /** michael@0: * This is the public API that UI and developers should be calling. All methods michael@0: * just forward to AddonManagerInternal. michael@0: */ michael@0: this.AddonManager = { michael@0: // Constants for the AddonInstall.state property michael@0: // The install is available for download. michael@0: STATE_AVAILABLE: 0, michael@0: // The install is being downloaded. michael@0: STATE_DOWNLOADING: 1, michael@0: // The install is checking for compatibility information. michael@0: STATE_CHECKING: 2, michael@0: // The install is downloaded and ready to install. michael@0: STATE_DOWNLOADED: 3, michael@0: // The download failed. michael@0: STATE_DOWNLOAD_FAILED: 4, michael@0: // The add-on is being installed. michael@0: STATE_INSTALLING: 5, michael@0: // The add-on has been installed. michael@0: STATE_INSTALLED: 6, michael@0: // The install failed. michael@0: STATE_INSTALL_FAILED: 7, michael@0: // The install has been cancelled. michael@0: STATE_CANCELLED: 8, michael@0: michael@0: // Constants representing different types of errors while downloading an michael@0: // add-on. michael@0: // The download failed due to network problems. michael@0: ERROR_NETWORK_FAILURE: -1, michael@0: // The downloaded file did not match the provided hash. michael@0: ERROR_INCORRECT_HASH: -2, michael@0: // The downloaded file seems to be corrupted in some way. michael@0: ERROR_CORRUPT_FILE: -3, michael@0: // An error occured trying to write to the filesystem. michael@0: ERROR_FILE_ACCESS: -4, michael@0: michael@0: // These must be kept in sync with AddonUpdateChecker. michael@0: // No error was encountered. michael@0: UPDATE_STATUS_NO_ERROR: 0, michael@0: // The update check timed out michael@0: UPDATE_STATUS_TIMEOUT: -1, michael@0: // There was an error while downloading the update information. michael@0: UPDATE_STATUS_DOWNLOAD_ERROR: -2, michael@0: // The update information was malformed in some way. michael@0: UPDATE_STATUS_PARSE_ERROR: -3, michael@0: // The update information was not in any known format. michael@0: UPDATE_STATUS_UNKNOWN_FORMAT: -4, michael@0: // The update information was not correctly signed or there was an SSL error. michael@0: UPDATE_STATUS_SECURITY_ERROR: -5, michael@0: // The update was cancelled. michael@0: UPDATE_STATUS_CANCELLED: -6, michael@0: michael@0: // Constants to indicate why an update check is being performed michael@0: // Update check has been requested by the user. michael@0: UPDATE_WHEN_USER_REQUESTED: 1, michael@0: // Update check is necessary to see if the Addon is compatibile with a new michael@0: // version of the application. michael@0: UPDATE_WHEN_NEW_APP_DETECTED: 2, michael@0: // Update check is necessary because a new application has been installed. michael@0: UPDATE_WHEN_NEW_APP_INSTALLED: 3, michael@0: // Update check is a regular background update check. michael@0: UPDATE_WHEN_PERIODIC_UPDATE: 16, michael@0: // Update check is needed to check an Addon that is being installed. michael@0: UPDATE_WHEN_ADDON_INSTALLED: 17, michael@0: michael@0: // Constants for operations in Addon.pendingOperations michael@0: // Indicates that the Addon has no pending operations. michael@0: PENDING_NONE: 0, michael@0: // Indicates that the Addon will be enabled after the application restarts. michael@0: PENDING_ENABLE: 1, michael@0: // Indicates that the Addon will be disabled after the application restarts. michael@0: PENDING_DISABLE: 2, michael@0: // Indicates that the Addon will be uninstalled after the application restarts. michael@0: PENDING_UNINSTALL: 4, michael@0: // Indicates that the Addon will be installed after the application restarts. michael@0: PENDING_INSTALL: 8, michael@0: PENDING_UPGRADE: 16, michael@0: michael@0: // Constants for operations in Addon.operationsRequiringRestart michael@0: // Indicates that restart isn't required for any operation. michael@0: OP_NEEDS_RESTART_NONE: 0, michael@0: // Indicates that restart is required for enabling the addon. michael@0: OP_NEEDS_RESTART_ENABLE: 1, michael@0: // Indicates that restart is required for disabling the addon. michael@0: OP_NEEDS_RESTART_DISABLE: 2, michael@0: // Indicates that restart is required for uninstalling the addon. michael@0: OP_NEEDS_RESTART_UNINSTALL: 4, michael@0: // Indicates that restart is required for installing the addon. michael@0: OP_NEEDS_RESTART_INSTALL: 8, michael@0: michael@0: // Constants for permissions in Addon.permissions. michael@0: // Indicates that the Addon can be uninstalled. michael@0: PERM_CAN_UNINSTALL: 1, michael@0: // Indicates that the Addon can be enabled by the user. michael@0: PERM_CAN_ENABLE: 2, michael@0: // Indicates that the Addon can be disabled by the user. michael@0: PERM_CAN_DISABLE: 4, michael@0: // Indicates that the Addon can be upgraded. michael@0: PERM_CAN_UPGRADE: 8, michael@0: // Indicates that the Addon can be set to be optionally enabled michael@0: // on a case-by-case basis. michael@0: PERM_CAN_ASK_TO_ACTIVATE: 16, michael@0: michael@0: // General descriptions of where items are installed. michael@0: // Installed in this profile. michael@0: SCOPE_PROFILE: 1, michael@0: // Installed for all of this user's profiles. michael@0: SCOPE_USER: 2, michael@0: // Installed and owned by the application. michael@0: SCOPE_APPLICATION: 4, michael@0: // Installed for all users of the computer. michael@0: SCOPE_SYSTEM: 8, michael@0: // The combination of all scopes. michael@0: SCOPE_ALL: 15, michael@0: michael@0: // 1-15 are different built-in views for the add-on type michael@0: VIEW_TYPE_LIST: "list", michael@0: michael@0: TYPE_UI_HIDE_EMPTY: 16, michael@0: // Indicates that this add-on type supports the ask-to-activate state. michael@0: // That is, add-ons of this type can be set to be optionally enabled michael@0: // on a case-by-case basis. michael@0: TYPE_SUPPORTS_ASK_TO_ACTIVATE: 32, michael@0: michael@0: // Constants for Addon.applyBackgroundUpdates. michael@0: // Indicates that the Addon should not update automatically. michael@0: AUTOUPDATE_DISABLE: 0, michael@0: // Indicates that the Addon should update automatically only if michael@0: // that's the global default. michael@0: AUTOUPDATE_DEFAULT: 1, michael@0: // Indicates that the Addon should update automatically. michael@0: AUTOUPDATE_ENABLE: 2, michael@0: michael@0: // Constants for how Addon options should be shown. michael@0: // Options will be opened in a new window michael@0: OPTIONS_TYPE_DIALOG: 1, michael@0: // Options will be displayed within the AM detail view michael@0: OPTIONS_TYPE_INLINE: 2, michael@0: // Options will be displayed in a new tab, if possible michael@0: OPTIONS_TYPE_TAB: 3, michael@0: // Same as OPTIONS_TYPE_INLINE, but no Preferences button will be shown. michael@0: // Used to indicate that only non-interactive information will be shown. michael@0: OPTIONS_TYPE_INLINE_INFO: 4, michael@0: michael@0: // Constants for displayed or hidden options notifications michael@0: // Options notification will be displayed michael@0: OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed", michael@0: // Options notification will be hidden michael@0: OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden", michael@0: michael@0: // Constants for getStartupChanges, addStartupChange and removeStartupChange michael@0: // Add-ons that were detected as installed during startup. Doesn't include michael@0: // add-ons that were pending installation the last time the application ran. michael@0: STARTUP_CHANGE_INSTALLED: "installed", michael@0: // Add-ons that were detected as changed during startup. This includes an michael@0: // add-on moving to a different location, changing version or just having michael@0: // been detected as possibly changed. michael@0: STARTUP_CHANGE_CHANGED: "changed", michael@0: // Add-ons that were detected as uninstalled during startup. Doesn't include michael@0: // add-ons that were pending uninstallation the last time the application ran. michael@0: STARTUP_CHANGE_UNINSTALLED: "uninstalled", michael@0: // Add-ons that were detected as disabled during startup, normally because of michael@0: // an application change making an add-on incompatible. Doesn't include michael@0: // add-ons that were pending being disabled the last time the application ran. michael@0: STARTUP_CHANGE_DISABLED: "disabled", michael@0: // Add-ons that were detected as enabled during startup, normally because of michael@0: // an application change making an add-on compatible. Doesn't include michael@0: // add-ons that were pending being enabled the last time the application ran. michael@0: STARTUP_CHANGE_ENABLED: "enabled", michael@0: michael@0: // Constants for the Addon.userDisabled property michael@0: // Indicates that the userDisabled state of this add-on is currently michael@0: // ask-to-activate. That is, it can be conditionally enabled on a michael@0: // case-by-case basis. michael@0: STATE_ASK_TO_ACTIVATE: "askToActivate", michael@0: michael@0: #ifdef MOZ_EM_DEBUG michael@0: get __AddonManagerInternal__() { michael@0: return AddonManagerInternal; michael@0: }, michael@0: #endif michael@0: michael@0: getInstallForURL: function AM_getInstallForURL(aUrl, aCallback, aMimetype, michael@0: aHash, aName, aIcons, michael@0: aVersion, aLoadGroup) { michael@0: AddonManagerInternal.getInstallForURL(aUrl, aCallback, aMimetype, aHash, michael@0: aName, aIcons, aVersion, aLoadGroup); michael@0: }, michael@0: michael@0: getInstallForFile: function AM_getInstallForFile(aFile, aCallback, aMimetype) { michael@0: AddonManagerInternal.getInstallForFile(aFile, aCallback, aMimetype); michael@0: }, michael@0: michael@0: /** michael@0: * Gets an array of add-on IDs that changed during the most recent startup. michael@0: * michael@0: * @param aType michael@0: * The type of startup change to get michael@0: * @return An array of add-on IDs michael@0: */ michael@0: getStartupChanges: function AM_getStartupChanges(aType) { michael@0: if (!(aType in AddonManagerInternal.startupChanges)) michael@0: return []; michael@0: return AddonManagerInternal.startupChanges[aType].slice(0); michael@0: }, michael@0: michael@0: getAddonByID: function AM_getAddonByID(aID, aCallback) { michael@0: AddonManagerInternal.getAddonByID(aID, aCallback); michael@0: }, michael@0: michael@0: getAddonBySyncGUID: function AM_getAddonBySyncGUID(aGUID, aCallback) { michael@0: AddonManagerInternal.getAddonBySyncGUID(aGUID, aCallback); michael@0: }, michael@0: michael@0: getAddonsByIDs: function AM_getAddonsByIDs(aIDs, aCallback) { michael@0: AddonManagerInternal.getAddonsByIDs(aIDs, aCallback); michael@0: }, michael@0: michael@0: getAddonsWithOperationsByTypes: michael@0: function AM_getAddonsWithOperationsByTypes(aTypes, aCallback) { michael@0: AddonManagerInternal.getAddonsWithOperationsByTypes(aTypes, aCallback); michael@0: }, michael@0: michael@0: getAddonsByTypes: function AM_getAddonsByTypes(aTypes, aCallback) { michael@0: AddonManagerInternal.getAddonsByTypes(aTypes, aCallback); michael@0: }, michael@0: michael@0: getAllAddons: function AM_getAllAddons(aCallback) { michael@0: AddonManagerInternal.getAllAddons(aCallback); michael@0: }, michael@0: michael@0: getInstallsByTypes: function AM_getInstallsByTypes(aTypes, aCallback) { michael@0: AddonManagerInternal.getInstallsByTypes(aTypes, aCallback); michael@0: }, michael@0: michael@0: getAllInstalls: function AM_getAllInstalls(aCallback) { michael@0: AddonManagerInternal.getAllInstalls(aCallback); michael@0: }, michael@0: michael@0: mapURIToAddonID: function AM_mapURIToAddonID(aURI) { michael@0: return AddonManagerInternal.mapURIToAddonID(aURI); michael@0: }, michael@0: michael@0: isInstallEnabled: function AM_isInstallEnabled(aType) { michael@0: return AddonManagerInternal.isInstallEnabled(aType); michael@0: }, michael@0: michael@0: isInstallAllowed: function AM_isInstallAllowed(aType, aUri) { michael@0: return AddonManagerInternal.isInstallAllowed(aType, aUri); michael@0: }, michael@0: michael@0: installAddonsFromWebpage: function AM_installAddonsFromWebpage(aType, aSource, michael@0: aUri, aInstalls) { michael@0: AddonManagerInternal.installAddonsFromWebpage(aType, aSource, aUri, aInstalls); michael@0: }, michael@0: michael@0: addManagerListener: function AM_addManagerListener(aListener) { michael@0: AddonManagerInternal.addManagerListener(aListener); michael@0: }, michael@0: michael@0: removeManagerListener: function AM_removeManagerListener(aListener) { michael@0: AddonManagerInternal.removeManagerListener(aListener); michael@0: }, michael@0: michael@0: addInstallListener: function AM_addInstallListener(aListener) { michael@0: AddonManagerInternal.addInstallListener(aListener); michael@0: }, michael@0: michael@0: removeInstallListener: function AM_removeInstallListener(aListener) { michael@0: AddonManagerInternal.removeInstallListener(aListener); michael@0: }, michael@0: michael@0: addAddonListener: function AM_addAddonListener(aListener) { michael@0: AddonManagerInternal.addAddonListener(aListener); michael@0: }, michael@0: michael@0: removeAddonListener: function AM_removeAddonListener(aListener) { michael@0: AddonManagerInternal.removeAddonListener(aListener); michael@0: }, michael@0: michael@0: addTypeListener: function AM_addTypeListener(aListener) { michael@0: AddonManagerInternal.addTypeListener(aListener); michael@0: }, michael@0: michael@0: removeTypeListener: function AM_removeTypeListener(aListener) { michael@0: AddonManagerInternal.removeTypeListener(aListener); michael@0: }, michael@0: michael@0: get addonTypes() { michael@0: return AddonManagerInternal.addonTypes; michael@0: }, michael@0: michael@0: /** michael@0: * Determines whether an Addon should auto-update or not. michael@0: * michael@0: * @param aAddon michael@0: * The Addon representing the add-on michael@0: * @return true if the addon should auto-update, false otherwise. michael@0: */ michael@0: shouldAutoUpdate: function AM_shouldAutoUpdate(aAddon) { michael@0: if (!aAddon || typeof aAddon != "object") michael@0: throw Components.Exception("aAddon must be specified", michael@0: Cr.NS_ERROR_INVALID_ARG); michael@0: michael@0: if (!("applyBackgroundUpdates" in aAddon)) michael@0: return false; michael@0: if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) michael@0: return true; michael@0: if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) michael@0: return false; michael@0: return this.autoUpdateDefault; michael@0: }, michael@0: michael@0: get checkCompatibility() { michael@0: return AddonManagerInternal.checkCompatibility; michael@0: }, michael@0: michael@0: set checkCompatibility(aValue) { michael@0: AddonManagerInternal.checkCompatibility = aValue; michael@0: }, michael@0: michael@0: get strictCompatibility() { michael@0: return AddonManagerInternal.strictCompatibility; michael@0: }, michael@0: michael@0: set strictCompatibility(aValue) { michael@0: AddonManagerInternal.strictCompatibility = aValue; michael@0: }, michael@0: michael@0: get checkUpdateSecurityDefault() { michael@0: return AddonManagerInternal.checkUpdateSecurityDefault; michael@0: }, michael@0: michael@0: get checkUpdateSecurity() { michael@0: return AddonManagerInternal.checkUpdateSecurity; michael@0: }, michael@0: michael@0: set checkUpdateSecurity(aValue) { michael@0: AddonManagerInternal.checkUpdateSecurity = aValue; michael@0: }, michael@0: michael@0: get updateEnabled() { michael@0: return AddonManagerInternal.updateEnabled; michael@0: }, michael@0: michael@0: set updateEnabled(aValue) { michael@0: AddonManagerInternal.updateEnabled = aValue; michael@0: }, michael@0: michael@0: get autoUpdateDefault() { michael@0: return AddonManagerInternal.autoUpdateDefault; michael@0: }, michael@0: michael@0: set autoUpdateDefault(aValue) { michael@0: AddonManagerInternal.autoUpdateDefault = aValue; michael@0: }, michael@0: michael@0: get hotfixID() { michael@0: return AddonManagerInternal.hotfixID; michael@0: }, michael@0: michael@0: escapeAddonURI: function AM_escapeAddonURI(aAddon, aUri, aAppVersion) { michael@0: return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion); michael@0: } michael@0: }; michael@0: michael@0: // load the timestamps module into AddonManagerInternal michael@0: Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", AddonManagerInternal); michael@0: Object.freeze(AddonManagerInternal); michael@0: Object.freeze(AddonManagerPrivate); michael@0: Object.freeze(AddonManager);