diff --git a/services/sync/tests/unit/test_prefs_store.js b/services/sync/tests/unit/test_prefs_store.js index 545536e445f..e97a769ba08 100644 --- a/services/sync/tests/unit/test_prefs_store.js +++ b/services/sync/tests/unit/test_prefs_store.js @@ -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(), diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index b1bef1cd98c..1bf17c555af 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -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(); }, diff --git a/toolkit/mozapps/extensions/addonManager.js b/toolkit/mozapps/extensions/addonManager.js index 6a5a3b9f61a..b0bb9510ec6 100644 --- a/toolkit/mozapps/extensions/addonManager.js +++ b/toolkit/mozapps/extensions/addonManager.js @@ -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; - } }, /** diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index 9f006d30006..890208b7389 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -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"); } diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug514327_3.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug514327_3.js index 6e415f600f5..ac9fb7c7a65 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug514327_3.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug514327_3.js @@ -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(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js index 92e893206fe..722f151f21a 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js +++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug616841.js @@ -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(); diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js new file mode 100644 index 00000000000..60d74b4d883 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_shutdown.js @@ -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(); +} diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini index b45e879ad3a..f7ffa0e02b4 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini @@ -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"