Bug #1313298. Allow to reinstall extensions that were disabled by user, if user wants to do it.
draft
Bug #1313298. Allow to reinstall extensions that were disabled by user, if user wants to do it.
MozReview-Commit-ID: Jhq0MVVp26g
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -518,20 +518,26 @@ class AddonInternal {
return app;
}
async findBlocklistEntry() {
return Blocklist.getAddonBlocklistEntry(this.wrapper);
}
async updateBlocklistState(options = {}) {
- let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options;
+ let {applySoftBlock = true, oldAddon = null, updateDatabase = true, userIntentionalInstall = false} = options;
if (oldAddon) {
- this.userDisabled = oldAddon.userDisabled;
+ // If user manually reinstall existing extension of different version
+ // (it means that it's manually upgrading) we should enable it first.
+ // Case when user reinstalls existing extension of the SAME version treats
+ // differently and handled at XPIInstall.startInstall().
+ this.userDisabled = userIntentionalInstall && this.version !== oldAddon.version
+ ? false
+ : oldAddon.userDisabled;
this.softDisabled = oldAddon.softDisabled;
this.blocklistState = oldAddon.blocklistState;
}
let oldState = this.blocklistState;
let entry = await this.findBlocklistEntry();
let newState = entry ? entry.state : Services.blocklist.STATE_NOT_BLOCKED;
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -1353,16 +1353,19 @@ class AddonInstall {
this.version = options.version || null;
this.file = null;
this.ownsTempFile = null;
this.addon = null;
this.state = null;
+ // indicate that addon update is running by browser automatically
+ this.browserAutoAddonUpdate = options.browserAutoAddonUpdate || false;
+
XPIInstall.installs.add(this);
}
/**
* Starts installation of this add-on from whatever state it is currently at
* if possible.
*
* Note this method is overridden to handle additional state in
@@ -1666,16 +1669,25 @@ class AddonInstall {
this.postpone(resumeFn);
return;
}
this.state = AddonManager.STATE_READY;
this.install();
}
+ ifReinstallOfExistingSameVersionDisabledAddon() {
+ return this.existingAddon &&
+ this.existingAddon.location === this.location &&
+ this.existingAddon.version === this.addon.version &&
+ this.existingAddon.userDisabled &&
+ !this.existingAddon.active &&
+ !this.browserAutoAddonUpdate;
+ }
+
// TODO This relies on the assumption that we are always installing into the
// highest priority install location so the resulting add-on will be visible
// overriding any existing copy in another install location (bug 557710).
/**
* Installs the add-on into the install location.
*/
startInstall() {
this.state = AddonManager.STATE_INSTALLING;
@@ -1693,16 +1705,29 @@ class AddonInstall {
if (install.state == AddonManager.STATE_INSTALLED &&
install.location == this.location &&
install.addon.id == this.addon.id) {
logger.debug(`Cancelling previous pending install of ${install.addon.id}`);
install.cancel();
}
}
+ // reinstall existing user-disabled addon (of the same installed version)
+ // if addon is marked to be uninstalled - don't reinstall it (bug 1313298)
+ const reinstall = this.ifReinstallOfExistingSameVersionDisabledAddon();
+ if (reinstall && !this.existingAddon.pendingUninstall) {
+ (async () => {
+ // enable existing addon back and handle chrome settings API
+ await XPIInternal.BootstrapScope.get(this.existingAddon).enable(this.existingAddon);
+ this.state = AddonManager.STATE_INSTALLED;
+ this._callInstallListeners("onInstallEnded", this.existingAddon.wrapper);
+ })();
+ return;
+ }
+
let isUpgrade = this.existingAddon &&
this.existingAddon.location == this.location;
logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec);
AddonManagerPrivate.callAddonListeners("onInstalling",
this.addon.wrapper,
false);
@@ -1734,18 +1759,17 @@ class AddonInstall {
existingAddonID
});
// Update the metadata in the database
this.addon._sourceBundle = file;
this.addon.visible = true;
if (isUpgrade) {
- this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
- file.path);
+ this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon, file.path);
let state = this.location.get(this.addon.id);
if (state) {
state.syncWithDB(this.addon, true);
} else {
logger.warn("Unexpected missing XPI state for add-on ${id}", this.addon);
}
} else {
this.addon.active = (this.addon.visible && !this.addon.disabled);
@@ -1768,17 +1792,17 @@ class AddonInstall {
// Notify providers that a new theme has been enabled.
if (isTheme(this.addon.type) && this.addon.active)
AddonManagerPrivate.notifyAddonChanged(this.addon.id, this.addon.type);
};
this._startupPromise = (async () => {
if (this.existingAddon) {
await XPIInternal.BootstrapScope.get(this.existingAddon).update(
- this.addon, !this.addon.disabled, install);
+ this.addon, !this.addon.disabled, install);
} else {
await install();
await XPIInternal.BootstrapScope.get(this.addon).install(undefined, true);
}
})();
await this._startupPromise;
})().catch((e) => {
@@ -1793,16 +1817,17 @@ class AddonInstall {
this.addon.wrapper);
this._callInstallListeners("onInstallFailed");
}).then(() => {
this.removeTemporaryFile();
return this.location.installer.releaseStagingDir();
});
}
+
/**
* Stages an add-on for install.
*
* @param {boolean} restartRequired
* If true, the final installation will be deferred until the
* next app startup.
* @param {AddonInternal} stagedAddon
* The AddonInternal object for the staged install.
@@ -2358,17 +2383,17 @@ var DownloadAddonInstall = class extends
this.addon.updateDate = Date.now();
if (this.existingAddon) {
this.addon.existingAddonID = this.existingAddon.id;
this.addon.installDate = this.existingAddon.installDate;
} else {
this.addon.installDate = this.addon.updateDate;
}
- await this.addon.updateBlocklistState({oldAddon: this.existingAddon});
+ await this.addon.updateBlocklistState({oldAddon: this.existingAddon, userIntentionalInstall: !this.browserAutoAddonUpdate});
if (this._callInstallListeners("onDownloadEnded")) {
// If a listener changed our state then do not proceed with the install
if (this.state != AddonManager.STATE_DOWNLOADED)
return;
// proceed with the install state machine.
this.install();
@@ -2423,17 +2448,19 @@ function createUpdate(aCallback, aAddon,
if (url instanceof Ci.nsIFileURL) {
install = new LocalAddonInstall(aAddon.location, url, opts);
await install.init();
} else {
install = new DownloadAddonInstall(aAddon.location, url, opts);
}
try {
if (aUpdate.updateInfoURL)
- install.releaseNotesURI = Services.io.newURI(escapeAddonURI(aAddon, aUpdate.updateInfoURL));
+ // if extension is updating by background update and extension is disabled, we aren't going to reinstall disabled extensions
+ // so that we use browserAutoAddonUpdate param here
+ install = new DownloadAddonInstall(aAddon.location, url, {...opts, browserAutoAddonUpdate: true});
} catch (e) {
// If the releaseNotesURI cannot be parsed then just ignore it.
}
aCallback(install);
})();
}
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -1838,16 +1838,38 @@ class BootstrapScope {
if (updateCallback) {
await updateCallback();
}
this.addon = newAddon;
return this._install(reason, callUpdate, startup, extraArgs);
}
+
+ /**
+ * Update disabled state of extension by updating appropriate addon metadata in DB.
+ * If extension was disabled before, it then calls bootstrap scope's startup method
+ * with BOOTSTRAP_REASONS.ADDON_ENABLE reason.
+ * Along the way it notifies appropriate registered addon listeners (e.g. onEnabled, etc..).
+ *
+ * @param {Object} aAddon
+ * Addon that is going to be enabled.
+ * @param {function} [enableCallback]
+ * An optional callback function to call before enabling
+ * the old add-on.
+ * @returns {Promise<bool>}
+ * Resolves when all required bootstrap callbacks have
+ * completed.
+ */
+ async enable(aAddon, enableCallback) {
+ if (enableCallback) {
+ await enableCallback();
+ }
+ return XPIDatabase.updateAddonDisabledState(aAddon, false);
+ }
}
var XPIProvider = {
get name() {
return "XPIProvider";
},
BOOTSTRAP_REASONS: Object.freeze(BOOTSTRAP_REASONS),
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
@@ -1244,32 +1244,32 @@ add_task(async function run_manual_updat
await promiseRestartManager();
await Pmanual_update("2");
await promiseRestartManager();
[s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
- check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s3, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
- check_addon(s4, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s4, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
// Can't manually update to a hardblocked add-on
check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
await Pmanual_update("3");
await promiseRestartManager();
[s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
check_addon(s1, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
- check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(s2, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(s3, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
- check_addon(s4, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(s4, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
});
// Starts with add-ons blocked and then new versions are installed outside of
// the app to change them to unblocked.
add_task(async function run_manual_update_2_test() {
await promiseShutdownManager();
@@ -1309,31 +1309,31 @@ add_task(async function run_manual_updat
await promiseRestartManager();
await Pmanual_update("2");
await promiseRestartManager();
[s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
check_addon(s1, "2.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
- check_addon(s2, "2.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+ check_addon(s2, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
check_addon(s3, "2.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
// Can't manually update to a hardblocked add-on
check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
await promiseRestartManager();
await Pmanual_update("3");
await promiseRestartManager();
[s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
check_addon(s1, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
- check_addon(s2, "3.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+ check_addon(s2, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(s3, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(h, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
check_addon(r, "3.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
await s1.enable();
await s2.enable();
await s4.disable();
});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_install.js
@@ -699,17 +699,17 @@ add_task(async function test_15() {
install.addListener({
onInstallStarted() {
do_throw("Install should not have continued");
}
});
});
-// Verify that the userDisabled value carries over to the upgrade by default
+// on manual user intentional reinstall, addon should be enabled back
add_task(async function test_16() {
await promiseRestartManager();
let url = "http://example.com/addons/test_install2_1.xpi";
let aInstall = await AddonManager.getInstallForURL(url, "application/x-xpinstall");
await new Promise(resolve => {
aInstall.addListener({
onInstallStarted() {
@@ -739,26 +739,26 @@ add_task(async function test_16() {
onInstallEnded() {
resolve();
}
});
aInstall_2.install();
});
checkAddon("addon2@tests.mozilla.org", aInstall_2.addon, {
- userDisabled: true,
- isActive: false,
+ userDisabled: false,
+ isActive: true,
});
await promiseRestartManager();
let a2_2 = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
checkAddon("addon2@tests.mozilla.org", a2_2, {
- userDisabled: true,
- isActive: false,
+ userDisabled: false,
+ isActive: true,
});
await a2_2.uninstall();
});
// Verify that changing the userDisabled value before onInstallEnded works
add_task(async function test_17() {
await promiseRestartManager();
@@ -839,17 +839,18 @@ add_task(async function test_18() {
isActive: false,
});
let url_2 = "http://example.com/addons/test_install2_2.xpi";
let aInstall_2 = await AddonManager.getInstallForURL(url_2, "application/x-xpinstall");
await new Promise(resolve => {
aInstall_2.addListener({
onInstallStarted() {
- ok(aInstall_2.addon.userDisabled);
+ // userDisabled is set to false already while running XPIDatabase.updateBlocklistState
+ ok(!aInstall_2.addon.userDisabled);
aInstall_2.addon.enable();
},
onInstallEnded() {
resolve();
}
});
aInstall_2.install();
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_reinstall_disabled_addon.js
@@ -0,0 +1,344 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+ChromeUtils.import("resource://testing-common/httpd.js");
+
+var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
+
+const ID = "test_addon@tests.mozilla.org";
+const ID_UPDATE = "addon1@tests.mozilla.org";
+const url1 = "http://example.com/addons/test_install1_1.xpi";
+const url2 = "http://example.com/addons/test_install1_2.xpi";
+
+const ADDONS = {
+ test_install1_1: {
+ "install.rdf": {
+ id: ID,
+ version: "1.0",
+ name: "Test 1 Addon",
+ description: "Test Description",
+ bootstrap: true,
+
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "1"}],
+
+ },
+ "icon.png": "Fake icon image",
+ },
+ test_install1_2: {
+ "install.rdf": {
+ id: ID,
+ version: "2.0",
+ name: "Test 2 Addon",
+ description: "Test Description",
+ bootstrap: true,
+
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "1"}],
+ },
+ },
+ test_update: {
+ "install.rdf": {
+ id: ID_UPDATE,
+ version: "2.0",
+ bootstrap: true,
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "1"
+ }],
+ name: "Test Update 1",
+ },
+ },
+};
+
+const XPIS = {};
+
+for (let [name, files] of Object.entries(ADDONS)) {
+ XPIS[name] = AddonTestUtils.createTempXPIFile(files);
+ testserver.registerFile(`/addons/${name}.xpi`, XPIS[name]);
+}
+
+// The test extension uses an insecure update url.
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, false);
+
+add_task(async function setup() {
+ createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+ await promiseStartupManager();
+
+ // Make sure we only register once despite multiple calls
+ AddonManager.addInstallListener(InstallListener);
+ AddonManager.addAddonListener(AddonListener);
+ AddonManager.addInstallListener(InstallListener);
+ AddonManager.addAddonListener(AddonListener);
+
+ // Create and configure the HTTP server.
+ testserver.registerDirectory("/data/", do_get_file("data"));
+ testserver.registerPathHandler("/redirect", function(aRequest, aResponse) {
+ aResponse.setStatusLine(null, 301, "Moved Permanently");
+ let url = aRequest.host + ":" + aRequest.port + aRequest.queryString;
+ aResponse.setHeader("Location", "http://" + url);
+ });
+ gPort = testserver.identity.primaryPort;
+});
+
+
+// user intentionally reinstalls existing disabled addon of the same version, so we should enable it
+// no onInstalling nor onInstalled are fired
+add_task(async function reinstallExistingDisabledAddonSameVersion() {
+ prepare_test({
+ [ID]: [
+ ["onInstalling", false],
+ "onInstalled",
+ ]
+ }, [
+ "onNewInstall",
+ "onDownloadStarted",
+ "onDownloadEnded",
+ "onInstallStarted",
+ "onInstallEnded",
+ ]);
+ let aInstall = await AddonManager.getInstallForURL(url1, "application/x-xpinstall");
+ await aInstall.install();
+ ensure_test_completed();
+
+ await promiseRestartManager();
+
+ let a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(a1.isActive);
+ Assert.ok(!a1.userDisabled);
+
+ prepare_test({
+ [ID]: [
+ ["onDisabling", false],
+ "onDisabled"
+ ]
+ });
+ await a1.disable();
+ ensure_test_completed();
+
+ await promiseRestartManager();
+
+ a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(!a1.isActive);
+ Assert.ok(a1.userDisabled);
+
+ prepare_test({
+ [ID]: [
+ ["onEnabling", false],
+ "onEnabled",
+ ]
+ }, [
+ "onNewInstall",
+ "onDownloadStarted",
+ "onDownloadEnded",
+ "onInstallStarted",
+ "onInstallEnded",
+ ]);
+ aInstall = await AddonManager.getInstallForURL(url1, "application/x-xpinstall");
+ await aInstall.install();
+ ensure_test_completed();
+
+ a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(a1.isActive);
+ Assert.ok(!a1.userDisabled);
+
+ await promiseRestartManager();
+ a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.ok(a1.isActive);
+ Assert.ok(!a1.userDisabled);
+
+ await a1.uninstall();
+
+ await promiseRestartManager();
+
+ a1 = await promiseAddonByID(ID);
+ Assert.equal(a1, null);
+});
+
+// user intentionally reinstalls existing disabled addon of different version, so we should enable it too
+// look at the expected addon listeners fired to see the difference from the test above
+add_task(async function reinstallExistingDisabledAddonDifferentVersion() {
+ prepare_test({
+ [ID]: [
+ ["onInstalling", false],
+ "onInstalled",
+ ]
+ }, [
+ "onNewInstall",
+ "onDownloadStarted",
+ "onDownloadEnded",
+ "onInstallStarted",
+ "onInstallEnded",
+ ]);
+ let aInstall = await AddonManager.getInstallForURL(url1, "application/x-xpinstall");
+ await aInstall.install();
+ ensure_test_completed();
+
+ await promiseRestartManager();
+
+ let a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(a1.isActive);
+ Assert.ok(!a1.userDisabled);
+
+ prepare_test({
+ [ID]: [
+ ["onDisabling", false],
+ "onDisabled"
+ ]
+ });
+ await a1.disable();
+ ensure_test_completed();
+
+ await promiseRestartManager();
+
+ a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(!a1.isActive);
+ Assert.ok(a1.userDisabled);
+
+ prepare_test({
+ [ID]: [
+ ["onInstalling", false],
+ "onInstalled",
+ ]
+ }, [
+ "onNewInstall",
+ "onDownloadStarted",
+ "onDownloadEnded",
+ "onInstallStarted",
+ "onInstallEnded",
+ ]);
+ aInstall = await AddonManager.getInstallForURL(url2, "application/x-xpinstall");
+ await aInstall.install();
+ ensure_test_completed();
+
+ a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(a1.isActive);
+ Assert.ok(!a1.userDisabled);
+ Assert.equal(a1.version, "2.0");
+
+ await promiseRestartManager();
+ a1 = await promiseAddonByID(ID);
+ Assert.notEqual(a1, null);
+ Assert.ok(a1.isActive);
+ Assert.ok(!a1.userDisabled);
+
+ await a1.uninstall();
+
+ await promiseRestartManager();
+
+ a1 = await promiseAddonByID(ID);
+ Assert.equal(a1, null);
+});
+
+// disabled addon is updating by auto background check, but addon still should be disabled
+add_task(async function reinstallDisabledExistingAddonByBackgroundCheck() {
+ prepare_test({
+ [ID_UPDATE]: [
+ ["onInstalling", false],
+ "onInstalled",
+ ]
+ }, [
+ "onNewInstall",
+ "onInstallStarted",
+ "onInstallEnded",
+ ]);
+
+ await promiseInstallXPI({
+ id: [ID_UPDATE],
+ version: "1.0",
+ bootstrap: true,
+ updateURL: "http://example.com/data/test_update.json",
+ targetApplications: [{
+ id: "xpcshell@tests.mozilla.org",
+ minVersion: "1",
+ maxVersion: "1"
+ }],
+ name: "Test Addon 1",
+ });
+
+ let a1 = await AddonManager.getAddonByID(ID_UPDATE);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(a1.isActive);
+
+ prepare_test({
+ [ID_UPDATE]: [
+ ["onDisabling", false],
+ "onDisabled"
+ ]
+ });
+ await a1.disable();
+
+ a1 = await promiseAddonByID(ID_UPDATE);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.pendingOperations, AddonManager.PENDING_NONE);
+ Assert.ok(!a1.isActive);
+ Assert.ok(a1.userDisabled);
+
+ let install = await new Promise(resolve => {
+ prepare_test({}, [
+ "onNewInstall",
+ "onDownloadStarted",
+ "onDownloadEnded"
+ ], resolve);
+
+ AddonManagerInternal.backgroundUpdateCheck();
+ });
+
+ Assert.notEqual(install.existingAddon, null);
+ Assert.equal(install.existingAddon.id, ID_UPDATE);
+
+ await new Promise(resolve => {
+ prepare_test({
+ [ID_UPDATE]: [
+ ["onInstalling", false],
+ "onInstalled",
+ ]
+ }, [
+ "onInstallStarted",
+ "onInstallEnded",
+ ], resolve);
+ });
+
+ a1 = await AddonManager.getAddonByID(ID_UPDATE);
+ Assert.notEqual(a1, null);
+ Assert.equal(a1.version, "2.0");
+ Assert.ok(!a1.isActive);
+ Assert.ok(a1.userDisabled);
+
+ prepare_test({
+ [ID_UPDATE]: [
+ ["onUninstalling", false],
+ "onUninstalled",
+ ]
+ });
+ await a1.uninstall();
+ ensure_test_completed();
+
+ await promiseRestartManager();
+ a1 = await promiseAddonByID(ID_UPDATE);
+ Assert.equal(a1, null);
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -285,8 +285,9 @@ tags = webextensions
[test_webextension_install_syntax_error.js]
skip-if = appname == "thunderbird"
tags = webextensions
[test_webextension_langpack.js]
skip-if = appname == "thunderbird"
tags = webextensions
[test_webextension_theme.js]
tags = webextensions
+[test_reinstall_disabled_addon.js]