diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index 923d3d07213..64bdb20d29e 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -112,6 +112,7 @@ const PREF_SHOWN_SELECTION_UI = "extensions.shownSelectionUI"; const PREF_INTERPOSITION_ENABLED = "extensions.interposition.enabled"; const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet"; const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url"; +const PREF_E10S_BLOCK_ENABLE = "extensions.e10sBlocksEnabling"; const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; @@ -2744,11 +2745,19 @@ this.XPIProvider = { observe: function(aSubject, aTopic, aData) { XPIProvider._closing = true; for (let id in XPIProvider.bootstrappedAddons) { + // If no scope has been loaded for this add-on then there is no need + // to shut it down (should only happen when a bootstrapped add-on is + // pending enable) + if (!(id in XPIProvider.bootstrapScopes)) + continue; + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); file.persistentDescriptor = XPIProvider.bootstrappedAddons[id].descriptor; let addon = createAddonDetails(id, XPIProvider.bootstrappedAddons[id]); XPIProvider.callBootstrapMethod(addon, file, "shutdown", BOOTSTRAP_REASONS.APP_SHUTDOWN); + if (XPIProvider.bootstrappedAddons[id].disable) + delete XPIProvider.bootstrappedAddons[aId]; } Services.obs.removeObserver(this, "quit-application-granted"); } @@ -4229,6 +4238,41 @@ this.XPIProvider = { } }, + /** + * In some cases having add-ons active blocks e10s but turning off e10s + * requires a restart so some add-ons that are normally restartless will + * require a restart to install or enable. + * + * @param aAddon + * The add-on to test + * @return true if enabling the add-on requires a restart + */ + e10sBlocksEnabling: function(aAddon) { + // If the preference isn't set then don't block anything + if (!Preferences.get(PREF_E10S_BLOCK_ENABLE, false)) + return false; + + // If e10s isn't active then don't block anything + if (!Services.appinfo.browserTabsRemoteAutostart) + return false; + + // Only extensions change behaviour + if (aAddon.type != "extension") + return false; + + // The hotfix is exempt + let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined); + if (hotfixID && hotfixID == aAddon.id) + return false; + + // System add-ons are exempt + if (aAddon._installLocation.name == KEY_APP_SYSTEM_DEFAULTS || + aAddon._installLocation.name == KEY_APP_SYSTEM_ADDONS) + return false; + + return true; + }, + /** * Tests whether enabling an add-on will require a restart. * @@ -4263,6 +4307,9 @@ this.XPIProvider = { return aAddon.internalName != this.currentSkin; } + if (this.e10sBlocksEnabling(aAddon)) + return true; + return !aAddon.bootstrap; }, @@ -4353,6 +4400,9 @@ this.XPIProvider = { if (aAddon.disabled) return false; + if (this.e10sBlocksEnabling(aAddon)) + return true; + // Themes will require a restart (even if dynamic switching is enabled due // to some caching issues) and non-bootstrapped add-ons will require a // restart @@ -4724,6 +4774,23 @@ this.XPIProvider = { AddonManagerPrivate.callAddonListeners("onEnabled", wrapper); } } + else if (aAddon.bootstrap) { + // Something blocked the restartless add-on from enabling or disabling + // make sure it happens on the next startup + if (isDisabled) { + this.bootstrappedAddons[aAddon.id].disable = true; + } + else { + this.bootstrappedAddons[aAddon.id] = { + version: aAddon.version, + type: aAddon.type, + descriptor: aAddon._sourceBundle.persistentDescriptor, + multiprocessCompatible: aAddon.multiprocessCompatible, + runInSafeMode: canRunInSafeMode(aAddon), + }; + this.persistBootstrappedAddons(); + } + } } // Sync with XPIStates. diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js index d0051b240fe..476afab5591 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js @@ -262,6 +262,7 @@ function createAppInfo(id, name, version, platformVersion) { platformBuildID: "2007010101", // nsIXULRuntime + browserTabsRemoteAutostart: false, inSafeMode: false, logConsoleErrors: true, OS: "XPCShell", diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js b/toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js new file mode 100644 index 00000000000..b68676901a4 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_e10s_restartless.js @@ -0,0 +1,125 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const ID = "bootstrap1@tests.mozilla.org"; + +BootstrapMonitor.init(); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); + +startupManager(); + +function* check_normal() { + let install = yield new Promise(resolve => AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), resolve)); + yield promiseCompleteAllInstalls([install]); + do_check_eq(install.state, AddonManager.STATE_INSTALLED); + do_check_false(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL)); + + BootstrapMonitor.checkAddonInstalled(ID); + BootstrapMonitor.checkAddonStarted(ID); + + let addon = yield promiseAddonByID(ID); + do_check_eq(addon, install.addon); + + do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE)); + addon.userDisabled = true; + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_false(addon.isActive); + do_check_false(hasFlag(addon.pendingOperations, AddonManager.PENDING_DISABLE)); + + do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE)); + addon.userDisabled = false; + BootstrapMonitor.checkAddonStarted(ID); + do_check_true(addon.isActive); + do_check_false(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE)); + + do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL)); + addon.uninstall(); + BootstrapMonitor.checkAddonNotStarted(ID); + BootstrapMonitor.checkAddonNotInstalled(ID); + + restartManager(); +} + +// Installing the add-on normally doesn't require a restart +add_task(function*() { + gAppInfo.browserTabsRemoteAutostart = false; + Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", false); + + yield check_normal(); +}); + +// Enabling the pref doesn't change anything +add_task(function*() { + gAppInfo.browserTabsRemoteAutostart = false; + Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true); + + yield check_normal(); +}); + +// Default e10s doesn't change anything +add_task(function*() { + gAppInfo.browserTabsRemoteAutostart = true; + Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", false); + + yield check_normal(); +}); + +// Pref and e10s blocks install +add_task(function*() { + gAppInfo.browserTabsRemoteAutostart = true; + Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true); + + let install = yield new Promise(resolve => AddonManager.getInstallForFile(do_get_addon("test_bootstrap1_1"), resolve)); + yield promiseCompleteAllInstalls([install]); + do_check_eq(install.state, AddonManager.STATE_INSTALLED); + do_check_true(hasFlag(install.addon.pendingOperations, AddonManager.PENDING_INSTALL)); + + let addon = yield promiseAddonByID(ID); + do_check_eq(addon, null); + + yield promiseRestartManager(); + + BootstrapMonitor.checkAddonInstalled(ID); + BootstrapMonitor.checkAddonStarted(ID); + + addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + + do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_DISABLE)); + addon.userDisabled = true; + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_false(addon.isActive); + do_check_false(hasFlag(addon.pendingOperations, AddonManager.PENDING_DISABLE)); + + do_check_true(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE)); + addon.userDisabled = false; + BootstrapMonitor.checkAddonNotStarted(ID); + do_check_false(addon.isActive); + do_check_true(hasFlag(addon.pendingOperations, AddonManager.PENDING_ENABLE)); + + yield promiseRestartManager(); + + addon = yield promiseAddonByID(ID); + do_check_neq(addon, null); + + do_check_true(addon.isActive); + BootstrapMonitor.checkAddonStarted(ID); + + do_check_false(hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_UNINSTALL)); + addon.uninstall(); + BootstrapMonitor.checkAddonNotStarted(ID); + BootstrapMonitor.checkAddonNotInstalled(ID); + + restartManager(); +}); + +// The hotfix is unaffected +add_task(function*() { + gAppInfo.browserTabsRemoteAutostart = true; + Services.prefs.setBoolPref("extensions.e10sBlocksEnabling", true); + Services.prefs.setCharPref("extensions.hotfix.id", ID); + + yield check_normal(); +}); diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini index 1e4ef8d2256..97bf8c84ff7 100644 --- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini +++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini @@ -303,3 +303,4 @@ run-sequentially = Uses global XCurProcD dir. skip-if = os != "win" [test_bug1180901.js] skip-if = os != "win" +[test_e10s_restartless.js]