Mirror of roytam1's UXP fork just in case Moonchild and Tobin decide to go after him
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2976 lines
99 KiB

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as
// most tests later register different nsIAppInfo implementations, which
// wouldn't be reflected in Services.appinfo anymore, as the lazy getter
// underlying it would have been initialized if we used it here.
if ("@mozilla.org/xre/app-info;1" in Cc) {
let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
// Refuse to run in child processes.
throw new Error("You cannot use the AddonManager in child processes!");
}
}
const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
const PREF_DEFAULT_PROVIDERS_ENABLED = "extensions.defaultProviders.enabled";
const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion";
const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility";
const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url";
const PREF_APP_UPDATE_ENABLED = "app.update.enabled";
const PREF_APP_UPDATE_AUTO = "app.update.auto";
const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
const PREF_SELECTED_LOCALE = "general.useragent.locale";
const UNKNOWN_XPCOM_ABI = "unknownABI";
const UPDATE_REQUEST_VERSION = 2;
const CATEGORY_UPDATE_PARAMS = "extension-update-params";
const XMLURI_BLOCKLIST = "http://www.mozilla.org/2006/addons-blocklist";
const KEY_PROFILEDIR = "ProfD";
const KEY_APPDIR = "XCurProcD";
const FILE_BLOCKLIST = "blocklist.xml";
const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
const PREF_EM_CHECK_COMPATIBILITY = "extensions.enableCompatibilityChecking";
const TOOLKIT_ID = "toolkit@mozilla.org";
const VALID_TYPES_REGEXP = /^[\w\-]+$/;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyGetter(this, "CertUtils", function certUtilsLazyGetter() {
let certUtils = {};
Components.utils.import("resource://gre/modules/CertUtils.jsm", certUtils);
return certUtils;
});
this.EXPORTED_SYMBOLS = [ "AddonManager", "AddonManagerPrivate" ];
const CATEGORY_PROVIDER_MODULE = "addon-provider-module";
// A list of providers to load by default
const DEFAULT_PROVIDERS = [
"resource://gre/modules/addons/XPIProvider.jsm",
"resource://gre/modules/LightweightThemeManager.jsm"
];
Cu.import("resource://gre/modules/Log.jsm");
// Configure a logger at the parent 'addons' level to format
// messages for all the modules under addons.*
const PARENT_LOGGER_ID = "addons";
var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
var formatter = new Log.BasicFormatter();
// Set parent logger (and its children) to append to
// the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));
// Set parent logger (and its children) to
// also append to standard out
parentLogger.addAppender(new Log.DumpAppender(formatter));
// Create a new logger (child of 'addons' logger)
// for use by the Addons Manager
const LOGGER_ID = "addons.manager";
var logger = Log.repository.getLogger(LOGGER_ID);
// Provide the ability to enable/disable logging
// messages at runtime.
// If the "extensions.logging.enabled" preference is
// missing or 'false', messages at the WARNING and higher
// severity should be logged to the JS console and standard error.
// If "extensions.logging.enabled" is set to 'true', messages
// at DEBUG and higher should go to JS console and standard error.
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
const UNNAMED_PROVIDER = "<unnamed-provider>";
function providerName(aProvider) {
return aProvider.name || UNNAMED_PROVIDER;
}
/**
* Preference listener which listens for a change in the
* "extensions.logging.enabled" preference and changes the logging level of the
* parent 'addons' level logger accordingly.
*/
var PrefObserver = {
init: function() {
Services.prefs.addObserver(PREF_LOGGING_ENABLED, this, false);
Services.obs.addObserver(this, "xpcom-shutdown", false);
this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "xpcom-shutdown") {
Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
Services.obs.removeObserver(this, "xpcom-shutdown");
}
else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
let debugLogEnabled = Services.prefs.getBoolPref(PREF_LOGGING_ENABLED, false);
if (debugLogEnabled) {
parentLogger.level = Log.Level.Debug;
}
else {
parentLogger.level = Log.Level.Warn;
}
}
}
};
PrefObserver.init();
/**
* Calls a callback method consuming any thrown exception. Any parameters after
* the callback parameter will be passed to the callback.
*
* @param aCallback
* The callback method to call
*/
function safeCall(aCallback, ...aArgs) {
try {
aCallback.apply(null, aArgs);
}
catch (e) {
logger.warn("Exception calling callback", e);
}
}
/**
* Report an exception thrown by a provider API method.
*/
function reportProviderError(aProvider, aMethod, aError) {
let method = `provider ${providerName(aProvider)}.${aMethod}`;
AddonManagerPrivate.recordException("AMI", method, aError);
logger.error("Exception calling " + method, aError);
}
/**
* Calls a method on a provider if it exists and consumes any thrown exception.
* Any parameters after the aDefault parameter are passed to the provider's method.
*
* @param aProvider
* The provider to call
* @param aMethod
* The method name to call
* @param aDefault
* A default return value if the provider does not implement the named
* method or throws an error.
* @return the return value from the provider, or aDefault if the provider does not
* implement method or throws an error
*/
function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
if (!(aMethod in aProvider))
return aDefault;
try {
return aProvider[aMethod].apply(aProvider, aArgs);
}
catch (e) {
reportProviderError(aProvider, aMethod, e);
return aDefault;
}
}
/**
* Calls a method on a provider if it exists and consumes any thrown exception.
* Parameters after aMethod are passed to aProvider.aMethod().
* The last parameter must be a callback function.
* If the provider does not implement the method, or the method throws, calls
* the callback with 'undefined'.
*
* @param aProvider
* The provider to call
* @param aMethod
* The method name to call
*/
function callProviderAsync(aProvider, aMethod, ...aArgs) {
let callback = aArgs[aArgs.length - 1];
if (!(aMethod in aProvider)) {
callback(undefined);
return;
}
try {
return aProvider[aMethod].apply(aProvider, aArgs);
}
catch (e) {
reportProviderError(aProvider, aMethod, e);
callback(undefined);
return;
}
}
/**
* Gets the currently selected locale for display.
* @return the selected locale or "en-US" if none is selected
*/
function getLocale() {
try {
if (Services.prefs.getBoolPref(PREF_MATCH_OS_LOCALE))
return Services.locale.getLocaleComponentForUserAgent();
}
catch (e) { }
try {
let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE,
Ci.nsIPrefLocalizedString);
if (locale)
return locale;
}
catch (e) { }
try {
return Services.prefs.getCharPref(PREF_SELECTED_LOCALE);
}
catch (e) { }
return "en-US";
}
/**
* Previously the APIs for installing add-ons from webpages accepted nsIURI
* arguments for the installing page. They now take an nsIPrincipal but for now
* maintain backwards compatibility by converting an nsIURI to an nsIPrincipal.
*
* @param aPrincipalOrURI
* The argument passed to the API function. Can be null, an nsIURI or
* an nsIPrincipal.
* @return an nsIPrincipal.
*/
function ensurePrincipal(principalOrURI) {
if (principalOrURI instanceof Ci.nsIPrincipal)
return principalOrURI;
logger.warn("Deprecated API call, please pass a non-null nsIPrincipal instead of an nsIURI");
// Previously a null installing URI meant allowing the install regardless.
if (!principalOrURI) {
return Services.scriptSecurityManager.getSystemPrincipal();
}
if (principalOrURI instanceof Ci.nsIURI) {
return Services.scriptSecurityManager.createCodebasePrincipal(principalOrURI, {
inBrowser: true
});
}
// Just return whatever we have, the API method will log an error about it.
return principalOrURI;
}
/**
* A helper class to repeatedly call a listener with each object in an array
* optionally checking whether the object has a method in it.
*
* @param aObjects
* The array of objects to iterate through
* @param aMethod
* An optional method name, if not null any objects without this method
* will not be passed to the listener
* @param aListener
* A listener implementing nextObject and noMoreObjects methods. The
* former will be called with the AsyncObjectCaller as the first
* parameter and the object as the second. noMoreObjects will be passed
* just the AsyncObjectCaller
*/
function AsyncObjectCaller(aObjects, aMethod, aListener) {
this.objects = [...aObjects];
this.method = aMethod;
this.listener = aListener;
this.callNext();
}
AsyncObjectCaller.prototype = {
objects: null,
method: null,
listener: null,
/**
* Passes the next object to the listener or calls noMoreObjects if there
* are none left.
*/
callNext: function() {
if (this.objects.length == 0) {
this.listener.noMoreObjects(this);
return;
}
let object = this.objects.shift();
if (!this.method || this.method in object)
this.listener.nextObject(this, object);
else
this.callNext();
}
};
/**
* Listens for a browser changing origin and cancels the installs that were
* started by it.
*/
function BrowserListener(aBrowser, aInstallingPrincipal, aInstalls) {
this.browser = aBrowser;
this.principal = aInstallingPrincipal;
this.installs = aInstalls;
this.installCount = aInstalls.length;
aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
Services.obs.addObserver(this, "message-manager-disconnect", true);
for (let install of this.installs)
install.addListener(this);
this.registered = true;
}
BrowserListener.prototype = {
browser: null,
installs: null,
installCount: null,
registered: false,
unregister: function() {
if (!this.registered)
return;
this.registered = false;
Services.obs.removeObserver(this, "message-manager-disconnect");
// The browser may have already been detached
if (this.browser.removeProgressListener)
this.browser.removeProgressListener(this);
for (let install of this.installs)
install.removeListener(this);
this.installs = null;
},
cancelInstalls: function() {
for (let install of this.installs) {
try {
install.cancel();
}
catch (e) {
// Some installs may have already failed or been cancelled, ignore these
}
}
},
observe: function(subject, topic, data) {
if (subject != this.browser.messageManager)
return;
// The browser's message manager has closed and so the browser is
// going away, cancel all installs
this.cancelInstalls();
},
onLocationChange: function(webProgress, request, location) {
if (this.browser.contentPrincipal && this.principal.subsumes(this.browser.contentPrincipal))
return;
// The browser has navigated to a new origin so cancel all installs
this.cancelInstalls();
},
onDownloadCancelled: function(install) {
// Don't need to hear more events from this install
install.removeListener(this);
// Once all installs have ended unregister everything
if (--this.installCount == 0)
this.unregister();
},
onDownloadFailed: function(install) {
this.onDownloadCancelled(install);
},
onInstallFailed: function(install) {
this.onDownloadCancelled(install);
},
onInstallEnded: function(install) {
this.onDownloadCancelled(install);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
Ci.nsIWebProgressListener,
Ci.nsIObserver])
};
/**
* This represents an author of an add-on (e.g. creator or developer)
*
* @param aName
* The name of the author
* @param aURL
* The URL of the author's profile page
*/
function AddonAuthor(aName, aURL) {
this.name = aName;
this.url = aURL;
}
AddonAuthor.prototype = {
name: null,
url: null,
// Returns the author's name, defaulting to the empty string
toString: function() {
return this.name || "";
}
}
/**
* This represents an screenshot for an add-on
*
* @param aURL
* The URL to the full version of the screenshot
* @param aWidth
* The width in pixels of the screenshot
* @param aHeight
* The height in pixels of the screenshot
* @param aThumbnailURL
* The URL to the thumbnail version of the screenshot
* @param aThumbnailWidth
* The width in pixels of the thumbnail version of the screenshot
* @param aThumbnailHeight
* The height in pixels of the thumbnail version of the screenshot
* @param aCaption
* The caption of the screenshot
*/
function AddonScreenshot(aURL, aWidth, aHeight, aThumbnailURL,
aThumbnailWidth, aThumbnailHeight, aCaption) {
this.url = aURL;
if (aWidth) this.width = aWidth;
if (aHeight) this.height = aHeight;
if (aThumbnailURL) this.thumbnailURL = aThumbnailURL;
if (aThumbnailWidth) this.thumbnailWidth = aThumbnailWidth;
if (aThumbnailHeight) this.thumbnailHeight = aThumbnailHeight;
if (aCaption) this.caption = aCaption;
}
AddonScreenshot.prototype = {
url: null,
width: null,
height: null,
thumbnailURL: null,
thumbnailWidth: null,
thumbnailHeight: null,
caption: null,
// Returns the screenshot URL, defaulting to the empty string
toString: function() {
return this.url || "";
}
}
/**
* This represents a compatibility override for an addon.
*
* @param aType
* Overrride type - "compatible" or "incompatible"
* @param aMinVersion
* Minimum version of the addon to match
* @param aMaxVersion
* Maximum version of the addon to match
* @param aAppID
* Application ID used to match appMinVersion and appMaxVersion
* @param aAppMinVersion
* Minimum version of the application to match
* @param aAppMaxVersion
* Maximum version of the application to match
*/
function AddonCompatibilityOverride(aType, aMinVersion, aMaxVersion, aAppID,
aAppMinVersion, aAppMaxVersion) {
this.type = aType;
this.minVersion = aMinVersion;
this.maxVersion = aMaxVersion;
this.appID = aAppID;
this.appMinVersion = aAppMinVersion;
this.appMaxVersion = aAppMaxVersion;
}
AddonCompatibilityOverride.prototype = {
/**
* Type of override - "incompatible" or "compatible".
* Only "incompatible" is supported for now.
*/
type: null,
/**
* Min version of the addon to match.
*/
minVersion: null,
/**
* Max version of the addon to match.
*/
maxVersion: null,
/**
* Application ID to match.
*/
appID: null,
/**
* Min version of the application to match.
*/
appMinVersion: null,
/**
* Max version of the application to match.
*/
appMaxVersion: null
};
/**
* A type of add-on, used by the UI to determine how to display different types
* of add-ons.
*
* @param aID
* The add-on type ID
* @param aLocaleURI
* The URI of a localized properties file to get the displayable name
* for the type from
* @param aLocaleKey
* The key for the string in the properties file or the actual display
* name if aLocaleURI is null. Include %ID% to include the type ID in
* the key
* @param aViewType
* The optional type of view to use in the UI
* @param aUIPriority
* The priority is used by the UI to list the types in order. Lower
* values push the type higher in the list.
* @param aFlags
* An option set of flags that customize the display of the add-on in
* the UI.
*/
function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags) {
if (!aID)
throw Components.Exception("An AddonType must have an ID", Cr.NS_ERROR_INVALID_ARG);
if (aViewType && aUIPriority === undefined)
throw Components.Exception("An AddonType with a defined view must have a set UI priority",
Cr.NS_ERROR_INVALID_ARG);
if (!aLocaleKey)
throw Components.Exception("An AddonType must have a displayable name",
Cr.NS_ERROR_INVALID_ARG);
this.id = aID;
this.uiPriority = aUIPriority;
this.viewType = aViewType;
this.flags = aFlags;
if (aLocaleURI) {
this.__defineGetter__("name", function nameGetter() {
delete this.name;
let bundle = Services.strings.createBundle(aLocaleURI);
this.name = bundle.GetStringFromName(aLocaleKey.replace("%ID%", aID));
return this.name;
});
}
else {
this.name = aLocaleKey;
}
}
var gStarted = false;
var gStartupComplete = false;
var gCheckCompatibility = true;
var gStrictCompatibility = true;
var gCheckUpdateSecurityDefault = true;
var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
var gUpdateEnabled = true;
var gAutoUpdateDefault = true;
var gShutdownBarrier = null;
var gRepoShutdownState = "";
var gShutdownInProgress = false;
/**
* This is the real manager, kept here rather than in AddonManager to keep its
* contents hidden from API users.
*/
var AddonManagerInternal = {
managerListeners: [],
installListeners: [],
addonListeners: [],
typeListeners: [],
pendingProviders: new Set(),
providers: new Set(),
providerShutdowns: new Map(),
types: {},
startupChanges: {},
// Store telemetry details per addon provider
telemetryDetails: {},
recordTimestamp: function(name, value) {
this.TelemetryTimestamps.add(name, value);
},
validateBlocklist: function() {
let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
// If there is no application shipped blocklist then there is nothing to do
if (!appBlocklist.exists())
return;
let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
// If there is no blocklist in the profile then copy the application shipped
// one there
if (!profileBlocklist.exists()) {
try {
appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
}
catch (e) {
logger.warn("Failed to copy the application shipped blocklist to the profile", e);
}
return;
}
let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
createInstance(Ci.nsIFileInputStream);
try {
let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Ci.nsIConverterInputStream);
fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
cstream.init(fileStream, "UTF-8", 0, 0);
let data = "";
let str = {};
let read = 0;
do {
read = cstream.readString(0xffffffff, str);
data += str.value;
} while (read != 0);
let parser = Cc["@mozilla.org/xmlextras/domparser;1"].
createInstance(Ci.nsIDOMParser);
var doc = parser.parseFromString(data, "text/xml");
}
catch (e) {
logger.warn("Application shipped blocklist could not be loaded", e);
return;
}
finally {
try {
fileStream.close();
}
catch (e) {
logger.warn("Unable to close blocklist file stream", e);
}
}
// If the namespace is incorrect then ignore the application shipped
// blocklist
if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
logger.warn("Application shipped blocklist has an unexpected namespace (" +
doc.documentElement.namespaceURI + ")");
return;
}
// If there is no lastupdate information then ignore the application shipped
// blocklist
if (!doc.documentElement.hasAttribute("lastupdate"))
return;
// If the application shipped blocklist is older than the profile blocklist
// then do nothing
if (doc.documentElement.getAttribute("lastupdate") <=
profileBlocklist.lastModifiedTime)
return;
// Otherwise copy the application shipped blocklist to the profile
try {
appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
}
catch (e) {
logger.warn("Failed to copy the application shipped blocklist to the profile", e);
}
},
/**
* Start up a provider, and register its shutdown hook if it has one
*/
_startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
logger.debug(`Starting provider: ${providerName(aProvider)}`);
callProvider(aProvider, "startup", null, aAppChanged, aOldAppVersion, aOldPlatformVersion);
if ('shutdown' in aProvider) {
let name = providerName(aProvider);
let AMProviderShutdown = () => {
// If the provider has been unregistered, it will have been removed from
// this.providers. If it hasn't been unregistered, then this is a normal
// shutdown - and we move it to this.pendingProviders incase we're
// running in a test that will start AddonManager again.
if (this.providers.has(aProvider)) {
this.providers.delete(aProvider);
this.pendingProviders.add(aProvider);
}
return new Promise((resolve, reject) => {
logger.debug("Calling shutdown blocker for " + name);
resolve(aProvider.shutdown());
})
.catch(err => {
logger.warn("Failure during shutdown of " + name, err);
AddonManagerPrivate.recordException("AMI", "Async shutdown of " + name, err);
});
};
logger.debug("Registering shutdown blocker for " + name);
this.providerShutdowns.set(aProvider, AMProviderShutdown);
AddonManager.shutdown.addBlocker(name, AMProviderShutdown);
}
this.pendingProviders.delete(aProvider);
this.providers.add(aProvider);
logger.debug(`Provider finished startup: ${providerName(aProvider)}`);
},
/**
* Initializes the AddonManager, loading any known providers and initializing
* them.
*/
startup: function() {
try {
if (gStarted)
return;
this.recordTimestamp("AMI_startup_begin");
// clear this for xpcshell test restarts
for (let provider in this.telemetryDetails)
delete this.telemetryDetails[provider];
let appChanged = undefined;
let oldAppVersion = null;
try {
oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
appChanged = Services.appinfo.version != oldAppVersion;
}
catch (e) { }
let oldPlatformVersion = Services.prefs.getCharPref(PREF_EM_LAST_PLATFORM_VERSION, "");
if (appChanged !== false) {
logger.debug("Application has been upgraded");
Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
Services.appinfo.version);
Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
Services.appinfo.platformVersion);
Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
(appChanged === undefined ? 0 : -1));
this.validateBlocklist();
}
gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY,
gCheckCompatibility);
Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this, false);
gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY,
gStrictCompatibility);
Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this, false);
let defaultBranch = Services.prefs.getDefaultBranch("");
gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
gCheckUpdateSecurityDefault);
gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY,
gCheckUpdateSecurity);
Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this, false);
gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED, gUpdateEnabled);
Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this, false);
gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT,
gAutoUpdateDefault);
Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this, false);
let defaultProvidersEnabled = Services.prefs.getBoolPref(PREF_DEFAULT_PROVIDERS_ENABLED, true);
AddonManagerPrivate.recordSimpleMeasure("default_providers", defaultProvidersEnabled);
// Ensure all default providers have had a chance to register themselves
if (defaultProvidersEnabled) {
for (let url of DEFAULT_PROVIDERS) {
try {
let scope = {};
Components.utils.import(url, scope);
// Sanity check - make sure the provider exports a symbol that
// has a 'startup' method
let syms = Object.keys(scope);
if ((syms.length < 1) ||
(typeof scope[syms[0]].startup != "function")) {
logger.warn("Provider " + url + " has no startup()");
AddonManagerPrivate.recordException("AMI", "provider " + url, "no startup()");
}
logger.debug("Loaded provider scope for " + url + ": " + Object.keys(scope).toSource());
}
catch (e) {
AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
logger.error("Exception loading default provider \"" + url + "\"", e);
}
};
}
// Load any providers registered in the category manager
let catman = Cc["@mozilla.org/categorymanager;1"].
getService(Ci.nsICategoryManager);
let entries = catman.enumerateCategory(CATEGORY_PROVIDER_MODULE);
while (entries.hasMoreElements()) {
let entry = entries.getNext().QueryInterface(Ci.nsISupportsCString).data;
let url = catman.getCategoryEntry(CATEGORY_PROVIDER_MODULE, entry);
try {
Components.utils.import(url, {});
logger.debug(`Loaded provider scope for ${url}`);
}
catch (e) {
AddonManagerPrivate.recordException("AMI", "provider " + url + " load failed", e);
logger.error("Exception loading provider " + entry + " from category \"" +
url + "\"", e);
}
}
// Register our shutdown handler with the AsyncShutdown manager
gShutdownBarrier = new AsyncShutdown.Barrier("AddonManager: Waiting for providers to shut down.");
AsyncShutdown.profileBeforeChange.addBlocker("AddonManager: shutting down.",
this.shutdownManager.bind(this),
{fetchState: this.shutdownState.bind(this)});
// Once we start calling providers we must allow all normal methods to work.
gStarted = true;
for (let provider of this.pendingProviders) {
this._startProvider(provider, appChanged, oldAppVersion, oldPlatformVersion);
}
// If this is a new profile just pretend that there were no changes
if (appChanged === undefined) {
for (let type in this.startupChanges)
delete this.startupChanges[type];
}
gStartupComplete = true;
this.recordTimestamp("AMI_startup_end");
}
catch (e) {
logger.error("startup failed", e);
AddonManagerPrivate.recordException("AMI", "startup failed", e);
}
logger.debug("Completed startup sequence");
this.callManagerListeners("onStartup");
},
/**
* Registers a new AddonProvider.
*
* @param aProvider
* The provider to register
* @param aTypes
* An optional array of add-on types
*/
registerProvider: function(aProvider, aTypes) {
if (!aProvider || typeof aProvider != "object")
throw Components.Exception("aProvider must be specified",
Cr.NS_ERROR_INVALID_ARG);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
this.pendingProviders.add(aProvider);
if (aTypes) {
aTypes.forEach(function(aType) {
if (!(aType.id in this.types)) {
if (!VALID_TYPES_REGEXP.test(aType.id)) {
logger.warn("Ignoring invalid type " + aType.id);
return;
}
this.types[aType.id] = {
type: aType,
providers: [aProvider]
};
let typeListeners = this.typeListeners.slice(0);
for (let listener of typeListeners) {
safeCall(function listenerSafeCall() {
listener.onTypeAdded(aType);
});
}
}
else {
this.types[aType.id].providers.push(aProvider);
}
}, this);
}
// If we're registering after startup call this provider's startup.
if (gStarted) {
this._startProvider(aProvider);
}
},
/**
* Unregisters an AddonProvider.
*
* @param aProvider
* The provider to unregister
* @return Whatever the provider's 'shutdown' method returns (if anything).
* For providers that have async shutdown methods returning Promises,
* the caller should wait for that Promise to resolve.
*/
unregisterProvider: function(aProvider) {
if (!aProvider || typeof aProvider != "object")
throw Components.Exception("aProvider must be specified",
Cr.NS_ERROR_INVALID_ARG);
this.providers.delete(aProvider);
// The test harness will unregister XPIProvider *after* shutdown, which is
// after the provider will have been moved from providers to
// pendingProviders.
this.pendingProviders.delete(aProvider);
for (let type in this.types) {
this.types[type].providers = this.types[type].providers.filter(function filterProvider(p) p != aProvider);
if (this.types[type].providers.length == 0) {
let oldType = this.types[type].type;
delete this.types[type];
let typeListeners = this.typeListeners.slice(0);
for (let listener of typeListeners) {
safeCall(function listenerSafeCall() {
listener.onTypeRemoved(oldType);
});
}
}
}
// If we're unregistering after startup but before shutting down,
// remove the blocker for this provider's shutdown and call it.
// If we're already shutting down, just let gShutdownBarrier call it to avoid races.
if (gStarted && !gShutdownInProgress) {
logger.debug("Unregistering shutdown blocker for " + providerName(aProvider));
let shutter = this.providerShutdowns.get(aProvider);
if (shutter) {
this.providerShutdowns.delete(aProvider);
gShutdownBarrier.client.removeBlocker(shutter);
return shutter();
}
}
return undefined;
},
/**
* Mark a provider as safe to access via AddonManager APIs, before its
* startup has completed.
*
* Normally a provider isn't marked as safe until after its (synchronous)
* startup() method has returned. Until a provider has been marked safe,
* it won't be used by any of the AddonManager APIs. markProviderSafe()
* allows a provider to mark itself as safe during its startup; this can be
* useful if the provider wants to perform tasks that block startup, which
* happen after its required initialization tasks and therefore when the
* provider is in a safe state.
*
* @param aProvider Provider object to mark safe
*/
markProviderSafe: function(aProvider) {
if (!gStarted) {
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
}
if (!aProvider || typeof aProvider != "object") {
throw Components.Exception("aProvider must be specified",
Cr.NS_ERROR_INVALID_ARG);
}
if (!this.pendingProviders.has(aProvider)) {
return;
}
this.pendingProviders.delete(aProvider);
this.providers.add(aProvider);
},
/**
* Calls a method on all registered providers if it exists and consumes any
* thrown exception. Return values are ignored. Any parameters after the
* method parameter are passed to the provider's method.
* WARNING: Do not use for asynchronous calls; callProviders() does not
* invoke callbacks if provider methods throw synchronous exceptions.
*
* @param aMethod
* The method name to call
* @see callProvider
*/
callProviders: function(aMethod, ...aArgs) {
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
let providers = [...this.providers];
for (let provider of providers) {
try {
if (aMethod in provider)
provider[aMethod].apply(provider, aArgs);
}
catch (e) {
reportProviderError(aProvider, aMethod, e);
}
}
},
/**
* Report the current state of asynchronous shutdown
*/
shutdownState() {
let state = [];
if (gShutdownBarrier) {
state.push({
name: gShutdownBarrier.client.name,
state: gShutdownBarrier.state
});
}
state.push({
name: "AddonRepository: async shutdown",
state: gRepoShutdownState
});
return state;
},
/**
* Shuts down the addon manager and all registered providers, this must clean
* up everything in order for automated tests to fake restarts.
* @return Promise{null} that resolves when all providers and dependent modules
* have finished shutting down
*/
shutdownManager: Task.async(function* () {
logger.debug("shutdown");
this.callManagerListeners("onShutdown");
gRepoShutdownState = "pending";
gShutdownInProgress = true;
// Clean up listeners
Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
let savedError = null;
// Only shut down providers if they've been started.
if (gStarted) {
try {
yield gShutdownBarrier.wait();
}
catch(err) {
savedError = err;
logger.error("Failure during wait for shutdown barrier", err);
AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonManager providers", err);
}
}
// Shut down AddonRepository after providers (if any).
try {
gRepoShutdownState = "in progress";
yield AddonRepository.shutdown();
gRepoShutdownState = "done";
}
catch(err) {
savedError = err;
logger.error("Failure during AddonRepository shutdown", err);
AddonManagerPrivate.recordException("AMI", "Async shutdown of AddonRepository", err);
}
logger.debug("Async provider shutdown done");
this.managerListeners.splice(0, this.managerListeners.length);
this.installListeners.splice(0, this.installListeners.length);
this.addonListeners.splice(0, this.addonListeners.length);
this.typeListeners.splice(0, this.typeListeners.length);
this.providerShutdowns.clear();
for (let type in this.startupChanges)
delete this.startupChanges[type];
gStarted = false;
gStartupComplete = false;
gShutdownBarrier = null;
gShutdownInProgress = false;
if (savedError) {
throw savedError;
}
}),
/**
* Notified when a preference we're interested in has changed.
*
* @see nsIObserver
*/
observe: function(aSubject, aTopic, aData) {
switch (aData) {
case PREF_EM_CHECK_COMPATIBILITY: {
let oldValue = gCheckCompatibility;
gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY, true);
this.callManagerListeners("onCompatibilityModeChanged");
if (gCheckCompatibility != oldValue)
this.updateAddonAppDisabledStates();
break;
}
case PREF_EM_STRICT_COMPATIBILITY: {
let oldValue = gStrictCompatibility;
gStrictCompatibility = Services.prefs.getBoolPref(PREF_EM_STRICT_COMPATIBILITY, true);
this.callManagerListeners("onCompatibilityModeChanged");
if (gStrictCompatibility != oldValue)
this.updateAddonAppDisabledStates();
break;
}
case PREF_EM_CHECK_UPDATE_SECURITY: {
let oldValue = gCheckUpdateSecurity;
gCheckUpdateSecurity = Services.prefs.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, true);
this.callManagerListeners("onCheckUpdateSecurityChanged");
if (gCheckUpdateSecurity != oldValue)
this.updateAddonAppDisabledStates();
break;
}
case PREF_EM_UPDATE_ENABLED: {
let oldValue = gUpdateEnabled;
gUpdateEnabled = Services.prefs.getBoolPref(PREF_EM_UPDATE_ENABLED, true);
this.callManagerListeners("onUpdateModeChanged");
break;
}
case PREF_EM_AUTOUPDATE_DEFAULT: {
let oldValue = gAutoUpdateDefault;
gAutoUpdateDefault = Services.prefs.getBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, true);
this.callManagerListeners("onUpdateModeChanged");
break;
}
}
},
/**
* Replaces %...% strings in an addon url (update and updateInfo) with
* appropriate values.
*
* @param aAddon
* The Addon representing the add-on
* @param aUri
* The string representation of the URI to escape
* @param aAppVersion
* The optional application version to use for %APP_VERSION%
* @return The appropriately escaped URI.
*/
escapeAddonURI: function(aAddon, aUri, aAppVersion)
{
if (!aAddon || typeof aAddon != "object")
throw Components.Exception("aAddon must be an Addon object",
Cr.NS_ERROR_INVALID_ARG);
if (!aUri || typeof aUri != "string")
throw Components.Exception("aUri must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aAppVersion && typeof aAppVersion != "string")
throw Components.Exception("aAppVersion must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
var addonStatus = aAddon.userDisabled || aAddon.softDisabled ? "userDisabled"
: "userEnabled";
if (!aAddon.isCompatible)
addonStatus += ",incompatible";
if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
addonStatus += ",blocklisted";
if (aAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
addonStatus += ",softblocked";
try {
var xpcomABI = Services.appinfo.XPCOMABI;
} catch (ex) {
xpcomABI = UNKNOWN_XPCOM_ABI;
}
let uri = aUri.replace(/%ITEM_ID%/g, aAddon.id);
uri = uri.replace(/%ITEM_VERSION%/g, aAddon.version);
uri = uri.replace(/%ITEM_STATUS%/g, addonStatus);
uri = uri.replace(/%APP_ID%/g, Services.appinfo.ID);
uri = uri.replace(/%APP_VERSION%/g, aAppVersion ? aAppVersion :
Services.appinfo.version);
uri = uri.replace(/%REQ_VERSION%/g, UPDATE_REQUEST_VERSION);
uri = uri.replace(/%APP_OS%/g, Services.appinfo.OS);
uri = uri.replace(/%APP_ABI%/g, xpcomABI);
uri = uri.replace(/%APP_LOCALE%/g, getLocale());
uri = uri.replace(/%CURRENT_APP_VERSION%/g, Services.appinfo.version);
// Replace custom parameters (names of custom parameters must have at
// least 3 characters to prevent lookups for something like %D0%C8)
var catMan = null;
uri = uri.replace(/%(\w{3,})%/g, function parameterReplace(aMatch, aParam) {
if (!catMan) {
catMan = Cc["@mozilla.org/categorymanager;1"].
getService(Ci.nsICategoryManager);
}
try {
var contractID = catMan.getCategoryEntry(CATEGORY_UPDATE_PARAMS, aParam);
var paramHandler = Cc[contractID].getService(Ci.nsIPropertyBag2);
return paramHandler.getPropertyAsAString(aParam);
}
catch(e) {
return aMatch;
}
});
// escape() does not properly encode + symbols in any embedded FVF strings.
return uri.replace(/\+/g, "%2B");
},
/**
* Performs a background update check by starting an update for all add-ons
* that can be updated.
* @return Promise{null} Resolves when the background update check is complete
* (the resulting addon installations may still be in progress).
*/
backgroundUpdateCheck: function() {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
let buPromise = Task.spawn(function* backgroundUpdateTask() {
logger.debug("Background update check beginning");
Services.obs.notifyObservers(null, "addons-background-update-start", null);
if (this.updateEnabled) {
let scope = {};
Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", scope);
scope.LightweightThemeManager.updateCurrentTheme();
let allAddons = yield new Promise((resolve, reject) => this.getAllAddons(resolve));
// Repopulate repository cache first, to ensure compatibility overrides
// are up to date before checking for addon updates.
yield AddonRepository.backgroundUpdateCheck();
// Keep track of all the async add-on updates happening in parallel
let updates = [];
for (let addon of allAddons) {
// Check all add-ons for updates so that any compatibility updates will
// be applied
updates.push(new Promise((resolve, reject) => {
addon.findUpdates({
onUpdateAvailable: function(aAddon, aInstall) {
// Start installing updates when the add-on can be updated and
// background updates should be applied.
logger.debug("Found update for add-on ${id}", aAddon);
if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE &&
AddonManager.shouldAutoUpdate(aAddon)) {
// XXX we really should resolve when this install is done,
// not when update-available check completes, no?
logger.debug("Starting install of ${id}", aAddon);
aInstall.install();
}
},
onUpdateFinished: aAddon => { logger.debug("onUpdateFinished for ${id}", aAddon); resolve(); }
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
}));
}
yield Promise.all(updates);
}
logger.debug("Background update check complete");
Services.obs.notifyObservers(null,
"addons-background-update-complete",
null);
}.bind(this));
// Fork the promise chain so we can log the error and let our caller see it too.
buPromise.then(null, e => logger.warn("Error in background update", e));
return buPromise;
},
/**
* Adds a add-on to the list of detected changes for this startup. If
* addStartupChange is called multiple times for the same add-on in the same
* startup then only the most recent change will be remembered.
*
* @param aType
* The type of change as a string. Providers can define their own
* types of changes or use the existing defined STARTUP_CHANGE_*
* constants
* @param aID
* The ID of the add-on
*/
addStartupChange: function(aType, aID) {
if (!aType || typeof aType != "string")
throw Components.Exception("aType must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (gStartupComplete)
return;
// Ensure that an ID is only listed in one type of change
for (let type in this.startupChanges)
this.removeStartupChange(type, aID);
if (!(aType in this.startupChanges))
this.startupChanges[aType] = [];
this.startupChanges[aType].push(aID);
},
/**
* Removes a startup change for an add-on.
*
* @param aType
* The type of change
* @param aID
* The ID of the add-on
*/
removeStartupChange: function(aType, aID) {
if (!aType || typeof aType != "string")
throw Components.Exception("aType must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (gStartupComplete)
return;
if (!(aType in this.startupChanges))
return;
this.startupChanges[aType] = this.startupChanges[aType].filter(
function filterItem(aItem) aItem != aID);
},
/**
* Calls all registered AddonManagerListeners with an event. Any parameters
* after the method parameter are passed to the listener.
*
* @param aMethod
* The method on the listeners to call
*/
callManagerListeners: function(aMethod, ...aArgs) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
let managerListeners = this.managerListeners.slice(0);
for (let listener of managerListeners) {
try {
if (aMethod in listener)
listener[aMethod].apply(listener, aArgs);
}
catch (e) {
logger.warn("AddonManagerListener threw exception when calling " + aMethod, e);
}
}
},
/**
* Calls all registered InstallListeners with an event. Any parameters after
* the extraListeners parameter are passed to the listener.
*
* @param aMethod
* The method on the listeners to call
* @param aExtraListeners
* An optional array of extra InstallListeners to also call
* @return false if any of the listeners returned false, true otherwise
*/
callInstallListeners: function(aMethod,
aExtraListeners, ...aArgs) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aExtraListeners && !Array.isArray(aExtraListeners))
throw Components.Exception("aExtraListeners must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
let result = true;
let listeners;
if (aExtraListeners)
listeners = aExtraListeners.concat(this.installListeners);
else
listeners = this.installListeners.slice(0);
for (let listener of listeners) {
try {
if (aMethod in listener) {
if (listener[aMethod].apply(listener, aArgs) === false)
result = false;
}
}
catch (e) {
logger.warn("InstallListener threw exception when calling " + aMethod, e);
}
}
return result;
},
/**
* Calls all registered AddonListeners with an event. Any parameters after
* the method parameter are passed to the listener.
*
* @param aMethod
* The method on the listeners to call
*/
callAddonListeners: function(aMethod, ...aArgs) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
let addonListeners = this.addonListeners.slice(0);
for (let listener of addonListeners) {
try {
if (aMethod in listener)
listener[aMethod].apply(listener, aArgs);
}
catch (e) {
logger.warn("AddonListener threw exception when calling " + aMethod, e);
}
}
},
/**
* Notifies all providers that an add-on has been enabled when that type of
* add-on only supports a single add-on being enabled at a time. This allows
* the providers to disable theirs if necessary.
*
* @param aID
* The ID of the enabled add-on
* @param aType
* The type of the enabled add-on
* @param aPendingRestart
* A boolean indicating if the change will only take place the next
* time the application is restarted
*/
notifyAddonChanged: function(aID, aType, aPendingRestart) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aID && typeof aID != "string")
throw Components.Exception("aID must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (!aType || typeof aType != "string")
throw Components.Exception("aType must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
// Temporary hack until bug 520124 lands.
// We can get here during synchronous startup, at which point it's
// considered unsafe (and therefore disallowed by AddonManager.jsm) to
// access providers that haven't been initialized yet. Since this is when
// XPIProvider is starting up, XPIProvider can't access itself via APIs
// going through AddonManager.jsm. Furthermore, LightweightThemeManager may
// not be initialized until after XPIProvider is, and therefore would also
// be unaccessible during XPIProvider startup. Thankfully, these are the
// only two uses of this API, and we know it's safe to use this API with
// both providers; so we have this hack to allow bypassing the normal
// safetey guard.
// The notifyAddonChanged/addonChanged API will be unneeded and therefore
// removed by bug 520124, so this is a temporary quick'n'dirty hack.
let providers = [...this.providers, ...this.pendingProviders];
for (let provider of providers) {
callProvider(provider, "addonChanged", null, aID, aType, aPendingRestart);
}
},
/**
* Notifies all providers they need to update the appDisabled property for
* their add-ons in response to an application change such as a blocklist
* update.
*/
updateAddonAppDisabledStates: function() {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
this.callProviders("updateAddonAppDisabledStates");
},
/**
* Notifies all providers that the repository has updated its data for
* installed add-ons.
*
* @param aCallback
* Function to call when operation is complete.
*/
updateAddonRepositoryData: function(aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "updateAddonRepositoryData", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "updateAddonRepositoryData",
aCaller.callNext.bind(aCaller));
},
noMoreObjects: function(aCaller) {
safeCall(aCallback);
// only tests should care about this
Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null);
}
});
},
/**
* Asynchronously gets an AddonInstall for a URL.
*
* @param aUrl
* The string represenation of the URL the add-on is located at
* @param aCallback
* A callback to pass the AddonInstall to
* @param aMimetype
* The mimetype of the add-on
* @param aHash
* An optional hash of the add-on
* @param aName
* An optional placeholder name while the add-on is being downloaded
* @param aIcons
* Optional placeholder icons while the add-on is being downloaded
* @param aVersion
* An optional placeholder version while the add-on is being downloaded
* @param aLoadGroup
* An optional nsILoadGroup to associate any network requests with
* @throws if the aUrl, aCallback or aMimetype arguments are not specified
*/
getInstallForURL: function(aUrl, aCallback, aMimetype,
aHash, aName, aIcons,
aVersion, aBrowser) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aUrl || typeof aUrl != "string")
throw Components.Exception("aURL must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aHash && typeof aHash != "string")
throw Components.Exception("aHash must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aName && typeof aName != "string")
throw Components.Exception("aName must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aIcons) {
if (typeof aIcons == "string")
aIcons = { "32": aIcons };
else if (typeof aIcons != "object")
throw Components.Exception("aIcons must be a string, an object or null",
Cr.NS_ERROR_INVALID_ARG);
} else {
aIcons = {};
}
if (aVersion && typeof aVersion != "string")
throw Components.Exception("aVersion must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
if (aBrowser && (!(aBrowser instanceof Ci.nsIDOMElement)))
throw Components.Exception("aBrowser must be a nsIDOMElement or null",
Cr.NS_ERROR_INVALID_ARG);
let providers = [...this.providers];
for (let provider of providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype)) {
callProviderAsync(provider, "getInstallForURL",
aUrl, aHash, aName, aIcons, aVersion, aBrowser,
function getInstallForURL_safeCall(aInstall) {
safeCall(aCallback, aInstall);
});
return;
}
}
safeCall(aCallback, null);
},
/**
* Asynchronously gets an AddonInstall for an nsIFile.
*
* @param aFile
* The nsIFile where the add-on is located
* @param aCallback
* A callback to pass the AddonInstall to
* @param aMimetype
* An optional mimetype hint for the add-on
* @throws if the aFile or aCallback arguments are not specified
*/
getInstallForFile: function(aFile, aCallback, aMimetype) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!(aFile instanceof Ci.nsIFile))
throw Components.Exception("aFile must be a nsIFile",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
if (aMimetype && typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "getInstallForFile", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "getInstallForFile", aFile,
function getInstallForFile_safeCall(aInstall) {
if (aInstall)
safeCall(aCallback, aInstall);
else
aCaller.callNext();
});
},
noMoreObjects: function(aCaller) {
safeCall(aCallback, null);
}
});
},
/**
* Asynchronously gets all current AddonInstalls optionally limiting to a list
* of types.
*
* @param aTypes
* An optional array of types to retrieve. Each type is a string name
* @param aCallback
* A callback which will be passed an array of AddonInstalls
* @throws If the aCallback argument is not specified
*/
getInstallsByTypes: function(aTypes, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let installs = [];
new AsyncObjectCaller(this.providers, "getInstallsByTypes", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "getInstallsByTypes", aTypes,
function getInstallsByTypes_safeCall(aProviderInstalls) {
if (aProviderInstalls) {
installs = installs.concat(aProviderInstalls);
}
aCaller.callNext();
});
},
noMoreObjects: function(aCaller) {
safeCall(aCallback, installs);
}
});
},
/**
* Asynchronously gets all current AddonInstalls.
*
* @param aCallback
* A callback which will be passed an array of AddonInstalls
*/
getAllInstalls: function(aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
this.getInstallsByTypes(null, aCallback);
},
/**
* Synchronously map a URI to the corresponding Addon ID.
*
* Mappable URIs are limited to in-application resources belonging to the
* add-on, such as Javascript compartments, XUL windows, XBL bindings, etc.
* but do not include URIs from meta data, such as the add-on homepage.
*
* @param aURI
* nsIURI to map to an addon id
* @return string containing the Addon ID or null
* @see amIAddonManager.mapURIToAddonID
*/
mapURIToAddonID: function(aURI) {
if (!(aURI instanceof Ci.nsIURI)) {
throw Components.Exception("aURI is not a nsIURI",
Cr.NS_ERROR_INVALID_ARG);
}
// Try all providers
let providers = [...this.providers];
for (let provider of providers) {
var id = callProvider(provider, "mapURIToAddonID", null, aURI);
if (id !== null) {
return id;
}
}
return null;
},
/**
* Checks whether installation is enabled for a particular mimetype.
*
* @param aMimetype
* The mimetype to check
* @return true if installation is enabled for the mimetype
*/
isInstallEnabled: function(aMimetype) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
let providers = [...this.providers];
for (let provider of providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
callProvider(provider, "isInstallEnabled"))
return true;
}
return false;
},
/**
* Checks whether a particular source is allowed to install add-ons of a
* given mimetype.
*
* @param aMimetype
* The mimetype of the add-on
* @param aInstallingPrincipal
* The nsIPrincipal that initiated the install
* @return true if the source is allowed to install this mimetype
*/
isInstallAllowed: function(aMimetype, aInstallingPrincipal) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
Cr.NS_ERROR_INVALID_ARG);
let providers = [...this.providers];
for (let provider of providers) {
if (callProvider(provider, "supportsMimetype", false, aMimetype) &&
callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal))
return true;
}
return false;
},
/**
* Starts installation of an array of AddonInstalls notifying the registered
* web install listener of blocked or started installs.
*
* @param aMimetype
* The mimetype of add-ons being installed
* @param aBrowser
* The optional browser element that started the installs
* @param aInstallingPrincipal
* The nsIPrincipal that initiated the install
* @param aInstalls
* The array of AddonInstalls to be installed
*/
installAddonsFromWebpage: function(aMimetype, aBrowser, aInstallingPrincipal, aInstalls) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (aBrowser && !(aBrowser instanceof Ci.nsIDOMElement))
throw Components.Exception("aSource must be a nsIDOMElement, or null",
Cr.NS_ERROR_INVALID_ARG);
if (!aInstallingPrincipal || !(aInstallingPrincipal instanceof Ci.nsIPrincipal))
throw Components.Exception("aInstallingPrincipal must be a nsIPrincipal",
Cr.NS_ERROR_INVALID_ARG);
if (!Array.isArray(aInstalls))
throw Components.Exception("aInstalls must be an array",
Cr.NS_ERROR_INVALID_ARG);
if (!("@mozilla.org/addons/web-install-listener;1" in Cc)) {
logger.warn("No web installer available, cancelling all installs");
aInstalls.forEach(function(aInstall) {
aInstall.cancel();
});
return;
}
// When a chrome in-content UI has loaded a <browser> inside to host a
// website we want to do our security checks on the inner-browser but
// notify front-end that install events came from the outer-browser (the
// main tab's browser). Check this by seeing if the browser we've been
// passed is in a content type docshell and if so get the outer-browser.
let topBrowser = aBrowser;
let docShell = aBrowser.ownerDocument.defaultView
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIDocShellTreeItem);
if (docShell.itemType == Ci.nsIDocShellTreeItem.typeContent)
topBrowser = docShell.chromeEventHandler;
try {
let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
getService(Ci.amIWebInstallListener);
if (!this.isInstallEnabled(aMimetype)) {
for (let install of aInstalls)
install.cancel();
weblistener.onWebInstallDisabled(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length);
return;
}
else if (!aBrowser.contentPrincipal || !aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)) {
for (let install of aInstalls)
install.cancel();
if (weblistener instanceof Ci.amIWebInstallListener2) {
weblistener.onWebInstallOriginBlocked(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length);
}
return;
}
// The installs may start now depending on the web install listener,
// listen for the browser navigating to a new origin and cancel the
// installs in that case.
new BrowserListener(aBrowser, aInstallingPrincipal, aInstalls);
if (!this.isInstallAllowed(aMimetype, aInstallingPrincipal)) {
if (weblistener.onWebInstallBlocked(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length)) {
aInstalls.forEach(function(aInstall) {
aInstall.install();
});
}
}
else if (weblistener.onWebInstallRequested(topBrowser, aInstallingPrincipal.URI,
aInstalls, aInstalls.length)) {
aInstalls.forEach(function(aInstall) {
aInstall.install();
});
}
}
catch (e) {
// In the event that the weblistener throws during instantiation or when
// calling onWebInstallBlocked or onWebInstallRequested all of the
// installs should get cancelled.
logger.warn("Failure calling web installer", e);
aInstalls.forEach(function(aInstall) {
aInstall.cancel();
});
}
},
/**
* Adds a new InstallListener if the listener is not already registered.
*
* @param aListener
* The InstallListener to add
*/
addInstallListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a InstallListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.installListeners.some(function addInstallListener_matchListener(i) {
return i == aListener; }))
this.installListeners.push(aListener);
},
/**
* Removes an InstallListener if the listener is registered.
*
* @param aListener
* The InstallListener to remove
*/
removeInstallListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a InstallListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.installListeners.length) {
if (this.installListeners[pos] == aListener)
this.installListeners.splice(pos, 1);
else
pos++;
}
},
/**
* Asynchronously gets an add-on with a specific ID.
*
* @param aID
* The ID of the add-on to retrieve
* @param aCallback
* The callback to pass the retrieved add-on to
* @throws if the aID or aCallback arguments are not specified
*/
getAddonByID: function(aID, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "getAddonByID", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "getAddonByID", aID,
function getAddonByID_safeCall(aAddon) {
if (aAddon)
safeCall(aCallback, aAddon);
else
aCaller.callNext();
});
},
noMoreObjects: function(aCaller) {
safeCall(aCallback, null);
}
});
},
/**
* Asynchronously get an add-on with a specific Sync GUID.
*
* @param aGUID
* String GUID of add-on to retrieve
* @param aCallback
* The callback to pass the retrieved add-on to.
* @throws if the aGUID or aCallback arguments are not specified
*/
getAddonBySyncGUID: function(aGUID, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aGUID || typeof aGUID != "string")
throw Components.Exception("aGUID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
new AsyncObjectCaller(this.providers, "getAddonBySyncGUID", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "getAddonBySyncGUID", aGUID,
function getAddonBySyncGUID_safeCall(aAddon) {
if (aAddon) {
safeCall(aCallback, aAddon);
} else {
aCaller.callNext();
}
});
},
noMoreObjects: function(aCaller) {
safeCall(aCallback, null);
}
});
},
/**
* Asynchronously gets an array of add-ons.
*
* @param aIDs
* The array of IDs to retrieve
* @param aCallback
* The callback to pass an array of Addons to
* @throws if the aID or aCallback arguments are not specified
*/
getAddonsByIDs: function(aIDs, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!Array.isArray(aIDs))
throw Components.Exception("aIDs must be an array",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let addons = [];
new AsyncObjectCaller(aIDs, null, {
nextObject: function(aCaller, aID) {
AddonManagerInternal.getAddonByID(aID,
function getAddonsByIDs_getAddonByID(aAddon) {
addons.push(aAddon);
aCaller.callNext();
});
},
noMoreObjects: function(aCaller) {
safeCall(aCallback, addons);
}
});
},
/**
* Asynchronously gets add-ons of specific types.
*
* @param aTypes
* An optional array of types to retrieve. Each type is a string name
* @param aCallback
* The callback to pass an array of Addons to.
* @throws if the aCallback argument is not specified
*/
getAddonsByTypes: function(aTypes, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let addons = [];
new AsyncObjectCaller(this.providers, "getAddonsByTypes", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "getAddonsByTypes", aTypes,
function getAddonsByTypes_concatAddons(aProviderAddons) {
if (aProviderAddons) {
addons = addons.concat(aProviderAddons);
}
aCaller.callNext();
});
},
noMoreObjects: function(aCaller) {
safeCall(aCallback, addons);
}
});
},
/**
* Asynchronously gets all installed add-ons.
*
* @param aCallback
* A callback which will be passed an array of Addons
*/
getAllAddons: function(aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
this.getAddonsByTypes(null, aCallback);
},
/**
* Asynchronously gets add-ons that have operations waiting for an application
* restart to complete.
*
* @param aTypes
* An optional array of types to retrieve. Each type is a string name
* @param aCallback
* The callback to pass the array of Addons to
* @throws if the aCallback argument is not specified
*/
getAddonsWithOperationsByTypes: function(aTypes, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
let addons = [];
new AsyncObjectCaller(this.providers, "getAddonsWithOperationsByTypes", {
nextObject: function(aCaller, aProvider) {
callProviderAsync(aProvider, "getAddonsWithOperationsByTypes", aTypes,
function getAddonsWithOperationsByTypes_concatAddons
(aProviderAddons) {
if (aProviderAddons) {
addons = addons.concat(aProviderAddons);
}
aCaller.callNext();
});
},
noMoreObjects: function(caller) {
safeCall(aCallback, addons);
}
});
},
/**
* Adds a new AddonManagerListener if the listener is not already registered.
*
* @param aListener
* The listener to add
*/
addManagerListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonManagerListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.managerListeners.some(function addManagerListener_matchListener(i) {
return i == aListener; }))
this.managerListeners.push(aListener);
},
/**
* Removes an AddonManagerListener if the listener is registered.
*
* @param aListener
* The listener to remove
*/
removeManagerListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonManagerListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.managerListeners.length) {
if (this.managerListeners[pos] == aListener)
this.managerListeners.splice(pos, 1);
else
pos++;
}
},
/**
* Adds a new AddonListener if the listener is not already registered.
*
* @param aListener
* The AddonListener to add
*/
addAddonListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.addonListeners.some(function addAddonListener_matchListener(i) {
return i == aListener; }))
this.addonListeners.push(aListener);
},
/**
* Removes an AddonListener if the listener is registered.
*
* @param aListener
* The AddonListener to remove
*/
removeAddonListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be an AddonListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.addonListeners.length) {
if (this.addonListeners[pos] == aListener)
this.addonListeners.splice(pos, 1);
else
pos++;
}
},
/**
* Adds a new TypeListener if the listener is not already registered.
*
* @param aListener
* The TypeListener to add
*/
addTypeListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a TypeListener object",
Cr.NS_ERROR_INVALID_ARG);
if (!this.typeListeners.some(function addTypeListener_matchListener(i) {
return i == aListener; }))
this.typeListeners.push(aListener);
},
/**
* Removes an TypeListener if the listener is registered.
*
* @param aListener
* The TypeListener to remove
*/
removeTypeListener: function(aListener) {
if (!aListener || typeof aListener != "object")
throw Components.Exception("aListener must be a TypeListener object",
Cr.NS_ERROR_INVALID_ARG);
let pos = 0;
while (pos < this.typeListeners.length) {
if (this.typeListeners[pos] == aListener)
this.typeListeners.splice(pos, 1);
else
pos++;
}
},
get addonTypes() {
// A read-only wrapper around the types dictionary
return new Proxy(this.types, {
defineProperty(target, property, descriptor) {
// Not allowed to define properties
return false;
},
deleteProperty(target, property) {
// Not allowed to delete properties
return false;
},
get(target, property, receiver) {
if (!target.hasOwnProperty(property))
return undefined;
return target[property].type;
},
getOwnPropertyDescriptor(target, property) {
if (!target.hasOwnProperty(property))
return undefined;
return {
value: target[property].type,
writable: false,
// Claim configurability to maintain the proxy invariants.
configurable: true,
enumerable: true
}
},
preventExtensions(target) {
// Not allowed to prevent adding new properties
return false;
},
set(target, property, value, receiver) {
// Not allowed to set properties
return false;
},
setPrototypeOf(target, prototype) {
// Not allowed to change prototype
return false;
}
});
},
get autoUpdateDefault() {
return gAutoUpdateDefault;
},
set autoUpdateDefault(aValue) {
aValue = !!aValue;
if (aValue != gAutoUpdateDefault)
Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue);
return aValue;
},
get checkCompatibility() {
return gCheckCompatibility;
},
set checkCompatibility(aValue) {
aValue = !!aValue;
if (aValue != gCheckCompatibility) {
if (!aValue)
Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false);
else
Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY);
}
return aValue;
},
get strictCompatibility() {
return gStrictCompatibility;
},
set strictCompatibility(aValue) {
aValue = !!aValue;
if (aValue != gStrictCompatibility)
Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue);
return aValue;
},
get checkUpdateSecurityDefault() {
return gCheckUpdateSecurityDefault;
},
get checkUpdateSecurity() {
return gCheckUpdateSecurity;
},
set checkUpdateSecurity(aValue) {
aValue = !!aValue;
if (aValue != gCheckUpdateSecurity) {
if (aValue != gCheckUpdateSecurityDefault)
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue);
else
Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
}
return aValue;
},
get updateEnabled() {
return gUpdateEnabled;
},
set updateEnabled(aValue) {
aValue = !!aValue;
if (aValue != gUpdateEnabled)
Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue);
return aValue;
},
};
/**
* Should not be used outside of core Mozilla code. This is a private API for
* the startup and platform integration code to use. Refer to the methods on
* AddonManagerInternal for documentation however note that these methods are
* subject to change at any time.
*/
this.AddonManagerPrivate = {
startup: function() {
AddonManagerInternal.startup();
},
registerProvider: function(aProvider, aTypes) {
AddonManagerInternal.registerProvider(aProvider, aTypes);
},
unregisterProvider: function(aProvider) {
AddonManagerInternal.unregisterProvider(aProvider);
},
markProviderSafe: function(aProvider) {
AddonManagerInternal.markProviderSafe(aProvider);
},
backgroundUpdateCheck: function() {
return AddonManagerInternal.backgroundUpdateCheck();
},
backgroundUpdateTimerHandler() {
// Don't call through to the real update check if no checks are enabled.
if (!AddonManagerInternal.updateEnabled) {
logger.info("Skipping background update check");
return;
}
// Don't return the promise here, since the caller doesn't care.
AddonManagerInternal.backgroundUpdateCheck();
},
addStartupChange: function(aType, aID) {
AddonManagerInternal.addStartupChange(aType, aID);
},
removeStartupChange: function(aType, aID) {
AddonManagerInternal.removeStartupChange(aType, aID);
},
notifyAddonChanged: function(aID, aType, aPendingRestart) {
AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart);
},
updateAddonAppDisabledStates: function() {
AddonManagerInternal.updateAddonAppDisabledStates();
},
updateAddonRepositoryData: function(aCallback) {
AddonManagerInternal.updateAddonRepositoryData(aCallback);
},
callInstallListeners: function(...aArgs) {
return AddonManagerInternal.callInstallListeners.apply(AddonManagerInternal,
aArgs);
},
callAddonListeners: function(...aArgs) {
AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);
},
AddonAuthor: AddonAuthor,
AddonScreenshot: AddonScreenshot,
AddonCompatibilityOverride: AddonCompatibilityOverride,
AddonType: AddonType,
recordTimestamp: function(name, value) {