Bug 782881 - Protect against attempts to use the Add-ons Manager APIs after shutdown. r=Unfocused

Bug 782881 - Protect against attempts to use the Add-ons Manager APIs after shutdown. r=Unfocused
This commit is contained in:
Dave Townsend 2012-05-10 11:33:02 -07:00
parent 7ca24186f5
commit a47d2f37ce
8 changed files with 175 additions and 23 deletions

View File

@ -7,6 +7,9 @@ Cu.import("resource://gre/modules/Services.jsm");
const PREFS_GUID = CommonUtils.encodeBase64URL(Services.appinfo.ID);
loadAddonTestFunctions();
startupManager();
function makePersona(id) {
return {
id: id || Math.random().toString(),

View File

@ -367,6 +367,7 @@ function AddonType(aID, aLocaleURI, aLocaleKey, aViewType, aUIPriority, aFlags)
}
var gStarted = false;
var gStartupComplete = false;
var gCheckCompatibility = true;
var gStrictCompatibility = true;
var gCheckUpdateSecurityDefault = true;
@ -443,6 +444,8 @@ var AddonManagerInternal = {
if (gStarted)
return;
Services.obs.addObserver(this, "xpcom-shutdown", false);
let appChanged = undefined;
let oldAppVersion = null;
@ -535,6 +538,9 @@ var AddonManagerInternal = {
}
}
// Once we start calling providers we must allow all normal methods to work.
gStarted = true;
this.callProviders("startup", appChanged, oldAppVersion,
oldPlatformVersion);
@ -544,7 +550,7 @@ var AddonManagerInternal = {
delete this.startupChanges[type];
}
gStarted = true;
gStartupComplete = true;
},
/**
@ -667,6 +673,8 @@ var AddonManagerInternal = {
* up everything in order for automated tests to fake restarts.
*/
shutdown: function AMI_shutdown() {
LOG("shutdown");
Services.obs.removeObserver(this, "xpcom-shutdown");
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);
@ -674,7 +682,10 @@ var AddonManagerInternal = {
Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
this.callProviders("shutdown");
// Always clean up listeners, but only shutdown providers if they've been
// started.
if (gStarted)
this.callProviders("shutdown");
this.managerListeners.splice(0, this.managerListeners.length);
this.installListeners.splice(0, this.installListeners.length);
@ -683,6 +694,7 @@ var AddonManagerInternal = {
for (let type in this.startupChanges)
delete this.startupChanges[type];
gStarted = false;
gStartupComplete = false;
},
/**
@ -691,6 +703,11 @@ var AddonManagerInternal = {
* @see nsIObserver
*/
observe: function AMI_observe(aSubject, aTopic, aData) {
if (aTopic == "xpcom-shutdown") {
this.shutdown();
return;
}
switch (aData) {
case PREF_EM_CHECK_COMPATIBILITY: {
let oldValue = gCheckCompatibility;
@ -852,6 +869,10 @@ var AddonManagerInternal = {
* that can be updated.
*/
backgroundUpdateCheck: function AMI_backgroundUpdateCheck() {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
let hotfixID = this.hotfixID;
let checkHotfix = hotfixID &&
@ -1033,7 +1054,7 @@ var AddonManagerInternal = {
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (gStarted)
if (gStartupComplete)
return;
// Ensure that an ID is only listed in one type of change
@ -1062,7 +1083,7 @@ var AddonManagerInternal = {
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
if (gStarted)
if (gStartupComplete)
return;
if (!(aType in this.startupChanges))
@ -1079,6 +1100,10 @@ var AddonManagerInternal = {
* The method on the listeners to call
*/
callManagerListeners: function AMI_callManagerListeners(aMethod, ...aArgs) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
@ -1106,6 +1131,10 @@ var AddonManagerInternal = {
* @return false if any of the listeners returned false, true otherwise
*/
callInstallListeners: function AMI_callInstallListeners(aMethod, aExtraListeners, ...aArgs) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
@ -1143,6 +1172,10 @@ var AddonManagerInternal = {
* The method on the listeners to call
*/
callAddonListeners: function AMI_callAddonListeners(aMethod, ...aArgs) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMethod || typeof aMethod != "string")
throw Components.Exception("aMethod must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
@ -1173,6 +1206,10 @@ var AddonManagerInternal = {
* time the application is restarted
*/
notifyAddonChanged: function AMI_notifyAddonChanged(aID, aType, aPendingRestart) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aID && typeof aID != "string")
throw Components.Exception("aID must be a string or null",
Cr.NS_ERROR_INVALID_ARG);
@ -1190,6 +1227,10 @@ var AddonManagerInternal = {
* update.
*/
updateAddonAppDisabledStates: function AMI_updateAddonAppDisabledStates() {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
this.callProviders("updateAddonAppDisabledStates");
},
@ -1201,6 +1242,10 @@ var AddonManagerInternal = {
* Function to call when operation is complete.
*/
updateAddonRepositoryData: function AMI_updateAddonRepositoryData(aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
@ -1242,6 +1287,10 @@ var AddonManagerInternal = {
getInstallForURL: function AMI_getInstallForURL(aUrl, aCallback, aMimetype,
aHash, aName, aIconURL,
aVersion, aLoadGroup) {
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);
@ -1300,6 +1349,10 @@ var AddonManagerInternal = {
* @throws if the aFile or aCallback arguments are not specified
*/
getInstallForFile: function AMI_getInstallForFile(aFile, aCallback, aMimetype) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!(aFile instanceof Ci.nsIFile))
throw Components.Exception("aFile must be a nsIFile",
Cr.NS_ERROR_INVALID_ARG);
@ -1340,6 +1393,10 @@ var AddonManagerInternal = {
* @throws If the aCallback argument is not specified
*/
getInstallsByTypes: function AMI_getInstallsByTypes(aTypes, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
@ -1372,6 +1429,10 @@ var AddonManagerInternal = {
* A callback which will be passed an array of AddonInstalls
*/
getAllInstalls: function AMI_getAllInstalls(aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
this.getInstallsByTypes(null, aCallback);
},
@ -1383,6 +1444,10 @@ var AddonManagerInternal = {
* @return true if installation is enabled for the mimetype
*/
isInstallEnabled: function AMI_isInstallEnabled(aMimetype) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aMimetype || typeof aMimetype != "string")
throw Components.Exception("aMimetype must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
@ -1407,6 +1472,10 @@ var AddonManagerInternal = {
* @return true if the source is allowed to install this mimetype
*/
isInstallAllowed: function AMI_isInstallAllowed(aMimetype, aURI) {
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);
@ -1441,6 +1510,10 @@ var AddonManagerInternal = {
aSource,
aURI,
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);
@ -1544,6 +1617,10 @@ var AddonManagerInternal = {
* @throws if the aID or aCallback arguments are not specified
*/
getAddonByID: function AMI_getAddonByID(aID, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aID || typeof aID != "string")
throw Components.Exception("aID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
@ -1578,6 +1655,10 @@ var AddonManagerInternal = {
* @throws if the aGUID or aCallback arguments are not specified
*/
getAddonBySyncGUID: function AMI_getAddonBySyncGUID(aGUID, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!aGUID || typeof aGUID != "string")
throw Components.Exception("aGUID must be a non-empty string",
Cr.NS_ERROR_INVALID_ARG);
@ -1613,6 +1694,10 @@ var AddonManagerInternal = {
* @throws if the aID or aCallback arguments are not specified
*/
getAddonsByIDs: function AMI_getAddonsByIDs(aIDs, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (!Array.isArray(aIDs))
throw Components.Exception("aIDs must be an array",
Cr.NS_ERROR_INVALID_ARG);
@ -1647,6 +1732,10 @@ var AddonManagerInternal = {
* @throws if the aCallback argument is not specified
*/
getAddonsByTypes: function AMI_getAddonsByTypes(aTypes, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
@ -1679,6 +1768,14 @@ var AddonManagerInternal = {
* A callback which will be passed an array of Addons
*/
getAllAddons: function AMI_getAllAddons(aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (typeof aCallback != "function")
throw Components.Exception("aCallback must be a function",
Cr.NS_ERROR_INVALID_ARG);
this.getAddonsByTypes(null, aCallback);
},
@ -1694,6 +1791,10 @@ var AddonManagerInternal = {
*/
getAddonsWithOperationsByTypes:
function AMI_getAddonsWithOperationsByTypes(aTypes, aCallback) {
if (!gStarted)
throw Components.Exception("AddonManager is not initialized",
Cr.NS_ERROR_NOT_INITIALIZED);
if (aTypes && !Array.isArray(aTypes))
throw Components.Exception("aTypes must be an array or null",
Cr.NS_ERROR_INVALID_ARG);
@ -1780,7 +1881,6 @@ var AddonManagerInternal = {
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)
@ -1920,10 +2020,6 @@ var AddonManagerPrivate = {
AddonManagerInternal.unregisterProvider(aProvider);
},
shutdown: function AMP_shutdown() {
AddonManagerInternal.shutdown();
},
backgroundUpdateCheck: function AMP_backgroundUpdateCheck() {
AddonManagerInternal.backgroundUpdateCheck();
},

View File

@ -49,19 +49,8 @@ function amManager() {
amManager.prototype = {
observe: function AMC_observe(aSubject, aTopic, aData) {
let os = Cc["@mozilla.org/observer-service;1"].
getService(Ci.nsIObserverService);
switch (aTopic) {
case "addons-startup":
os.addObserver(this, "xpcom-shutdown", false);
if (aTopic == "addons-startup")
AddonManagerPrivate.startup();
break;
case "xpcom-shutdown":
os.removeObserver(this, "xpcom-shutdown");
AddonManagerPrivate.shutdown();
break;
}
},
/**

View File

@ -393,7 +393,8 @@ function shutdownManager() {
}, "addon-repository-shutdown", false);
obs.notifyObservers(null, "quit-application-granted", null);
gInternalManager.observe(null, "xpcom-shutdown", null);
let scope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
scope.AddonManagerInternal.shutdown();
gInternalManager = null;
AddonRepository.shutdown();
@ -416,7 +417,7 @@ function shutdownManager() {
// Force the XPIProvider provider to reload to better
// simulate real-world usage.
let scope = Components.utils.import("resource://gre/modules/XPIProvider.jsm");
scope = Components.utils.import("resource://gre/modules/XPIProvider.jsm");
AddonManagerPrivate.unregisterProvider(scope.XPIProvider);
Components.utils.unload("resource://gre/modules/XPIProvider.jsm");
}

View File

@ -120,6 +120,7 @@ function run_test() {
gTestserver.registerDirectory("/data/", do_get_file("data"));
gTestserver.start(4444);
startupManager();
// initialize the blocklist with no entries
var blocklistFile = gProfD.clone();

View File

@ -13,6 +13,7 @@ function test_string_compare() {
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
startupManager();
do_test_pending();

View File

@ -0,0 +1,60 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Verify that API functions fail if the Add-ons Manager isn't initialised.
const IGNORE = ["escapeAddonURI", "shouldAutoUpdate", "getStartupChanges",
"addTypeListener", "removeTypeListener",
"addAddonListener", "removeAddonListener",
"addInstallListener", "removeInstallListener",
"addManagerListener", "removeManagerListener"];
const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
"AddonScreenshot", "AddonType", "startup", "shutdown",
"registerProvider", "unregisterProvider",
"addStartupChange", "removeStartupChange"];
function test_functions() {
for (let prop in AddonManager) {
if (typeof AddonManager[prop] != "function")
continue;
if (IGNORE.indexOf(prop) != -1)
continue;
try {
do_print("AddonManager." + prop);
AddonManager[prop]();
do_throw(prop + " did not throw an exception");
}
catch (e) {
if (e.result != Components.results.NS_ERROR_NOT_INITIALIZED)
do_throw(prop + " threw an unexpected exception: " + e);
}
}
for (let prop in AddonManagerPrivate) {
if (typeof AddonManagerPrivate[prop] != "function")
continue;
if (IGNORE_PRIVATE.indexOf(prop) != -1)
continue;
try {
do_print("AddonManagerPrivate." + prop);
AddonManagerPrivate[prop]();
do_throw(prop + " did not throw an exception");
}
catch (e) {
if (e.result != Components.results.NS_ERROR_NOT_INITIALIZED)
do_throw(prop + " threw an unexpected exception: " + e);
}
}
}
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
test_functions();
startupManager();
shutdownManager();
test_functions();
}

View File

@ -193,6 +193,7 @@ fail-if = os == "android"
[test_pref_properties.js]
[test_registry.js]
[test_safemode.js]
[test_shutdown.js]
[test_startup.js]
# Bug 676992: test consistently fails on Android
fail-if = os == "android"