Bug 1329021 - Stop an embedded webextension when a shutdown bootstrap method is not defined. r?aswan
MozReview-Commit-ID: 4VTDRpadaHO
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -4856,21 +4856,16 @@ this.XPIProvider = {
try {
method = Components.utils.evalInSandbox(`${aMethod};`,
activeAddon.bootstrapScope, "ECMAv5");
} catch (e) {
// An exception will be caught if the expected method is not defined.
// That will be logged below.
}
- if (!method) {
- logger.warn("Add-on " + aAddon.id + " is missing bootstrap method " + aMethod);
- return;
- }
-
// Extensions are automatically deinitialized in the correct order at shutdown.
if (aMethod == "shutdown" && aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
activeAddon.disable = true;
for (let addon of this.getDependentAddons(aAddon)) {
if (addon.active)
this.updateAddonDisabledState(addon);
}
}
@@ -4894,22 +4889,26 @@ this.XPIProvider = {
params.webExtension = {
startup: () => webExtension.startup(),
};
} else if (aMethod == "shutdown") {
LegacyExtensionsUtils.getEmbeddedExtensionFor(params).shutdown();
}
}
- logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " +
- aAddon.version);
- try {
- method(params, aReason);
- } catch (e) {
- logger.warn("Exception running bootstrap method " + aMethod + " on " + aAddon.id, e);
+ if (!method) {
+ logger.warn("Add-on " + aAddon.id + " is missing bootstrap method " + aMethod);
+ } else {
+ logger.debug("Calling bootstrap method " + aMethod + " on " + aAddon.id + " version " +
+ aAddon.version);
+ try {
+ method(params, aReason);
+ } catch (e) {
+ logger.warn("Exception running bootstrap method " + aMethod + " on " + aAddon.id, e);
+ }
}
} finally {
// Extensions are automatically initialized in the correct order at startup.
if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) {
for (let addon of this.getDependentAddons(aAddon))
this.updateAddonDisabledState(addon);
}
--- a/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/BootstrapMonitor.jsm
@@ -18,13 +18,13 @@ function notify(event, originalMethod, d
// If the bootstrap scope already declares a method call it
if (originalMethod)
originalMethod(data, reason);
}
// Allows a simple one-line bootstrap script:
// Components.utils.import("resource://xpcshelldata/bootstrapmonitor.jsm").monitor(this);
-this.monitor = function(scope) {
- for (let event of ["install", "startup", "shutdown", "uninstall"]) {
+this.monitor = function(scope, methods = ["install", "startup", "shutdown", "uninstall"]) {
+ for (let event of methods) {
scope[event] = notify.bind(null, event, scope[event]);
}
}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_embedded.js
@@ -42,16 +42,22 @@ function promiseWebExtensionShutdown() {
Management.on("shutdown", listener);
});
}
const BOOTSTRAP = String.raw`
Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);
`;
+const BOOTSTRAP_WITHOUT_SHUTDOWN = String.raw`
+ Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this, [
+ "install", "startup", "uninstall",
+ ]);
+`;
+
const EMBEDDED_WEBEXT_MANIFEST = JSON.stringify({
name: "embedded webextension addon",
manifest_version: 2,
version: "1.0",
});
/**
* This test case checks that an hasEmbeddedWebExtension addon property
@@ -298,8 +304,80 @@ add_task(function* reload_embedded_webex
let waitUninstalled = promiseAddonEvent("onUninstalled");
addon.uninstall();
yield waitUninstalled;
// No leaked embedded extension after uninstalling.
equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
"No embedded extension instance should be tracked after the addon uninstall");
});
+
+/**
+ * This test case checks that an addon with hasEmbeddedWebExtension without
+ * a bootstrap shutdown method stops the embedded webextension.
+ */
+add_task(function* shutdown_embedded_webext_without_bootstrap_shutdown() {
+ const ID = "embedded-webextension-without-shutdown@tests.mozilla.org";
+
+ // No embedded webextension should be currently around.
+ equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
+ "No embedded extension instance should be tracked here");
+
+ const xpiFile = createTempXPIFile({
+ id: ID,
+ name: "Test Add-on",
+ version: "1.0",
+ bootstrap: true,
+ hasEmbeddedWebExtension: true,
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "1.9.2"
+ }]
+ }, {
+ "bootstrap.js": BOOTSTRAP_WITHOUT_SHUTDOWN,
+ "webextension/manifest.json": EMBEDDED_WEBEXT_MANIFEST,
+ });
+
+ yield AddonManager.installTemporaryAddon(xpiFile);
+
+ let addon = yield promiseAddonByID(ID);
+
+ notEqual(addon, null, "Got an addon object as expected");
+ equal(addon.version, "1.0", "Got the expected version");
+ equal(addon.isActive, true, "The Addon is active");
+ equal(addon.appDisabled, false, "The addon is not app disabled");
+ equal(addon.userDisabled, false, "The addon is not user disabled");
+
+ // Check that the addon has been installed and started.
+ BootstrapMonitor.checkAddonInstalled(ID, "1.0");
+ BootstrapMonitor.checkAddonStarted(ID, "1.0");
+
+ // Only one embedded extension.
+ equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 1,
+ "Got the expected number of tracked extension instances");
+
+ const startupInfo = BootstrapMonitor.started.get(ID);
+
+ const waitForWebExtensionStartup = promiseWebExtensionStartup();
+
+ yield startupInfo.data.webExtension.startup();
+
+ // WebExtension startup should have been fully resolved.
+ yield waitForWebExtensionStartup;
+
+ // Fake the BootstrapMonitor notification, or the shutdown checks defined in head_addons.js
+ // will fail because of the missing shutdown method.
+ const fakeShutdownInfo = Object.assign(startupInfo, {
+ event: "shutdown",
+ reason: 2,
+ });
+ Services.obs.notifyObservers({}, "bootstrapmonitor-event", JSON.stringify(fakeShutdownInfo));
+
+ // Uninstall the addon.
+ const waitUninstalled = promiseAddonEvent("onUninstalled");
+ addon.uninstall();
+ yield waitUninstalled;
+
+ // No leaked embedded extension after uninstalling.
+ equal(EmbeddedExtensionManager.embeddedExtensionsByAddonId.size, 0,
+ "No embedded extension instance should be tracked after the addon uninstall");
+});