Bug 1269889 - make addon.reload() more like temp loading. r=kmag r=aswan

MozReview-Commit-ID: APK49tqcjTA

--HG--
extra : transplant_source : %100%F9q%BD%FF%3CP%89%EF%0Cz%27%3Cc%BCX%3B%F4%D7
This commit is contained in:
Kumar McMillan 2016-05-03 17:07:10 -05:00
Родитель 444ccbbdef
Коммит 3ed84ad41a
6 изменённых файлов: 378 добавлений и 101 удалений

Просмотреть файл

@ -31,35 +31,35 @@ add_task(function* () {
// Retrieve the Reload button.
const names = [...document.querySelectorAll("#addons .target-name")];
const name = names.filter(element => element.textContent === ADDON_NAME)[0];
ok(name, "Found " + ADDON_NAME + " add-on in the list");
ok(name, `Found ${ADDON_NAME} add-on in the list`);
const targetElement = name.parentNode.parentNode;
const reloadButton = targetElement.querySelector(".reload-button");
ok(reloadButton, "Found its reload button");
const onDisabled = promiseAddonEvent("onDisabled");
const onEnabled = promiseAddonEvent("onEnabled");
const onInstalled = promiseAddonEvent("onInstalled");
const onBootstrapInstallCalled = new Promise(done => {
Services.obs.addObserver(function listener() {
Services.obs.removeObserver(listener, ADDON_NAME, false);
ok(true, "Add-on was installed: " + ADDON_NAME);
info("Add-on was re-installed: " + ADDON_NAME);
done();
}, ADDON_NAME, false);
});
reloadButton.click();
const [disabledAddon] = yield onDisabled;
ok(disabledAddon.name === ADDON_NAME,
"Add-on was disabled: " + disabledAddon.name);
const [enabledAddon] = yield onEnabled;
ok(enabledAddon.name === ADDON_NAME,
"Add-on was re-enabled: " + enabledAddon.name);
const [reloadedAddon] = yield onInstalled;
is(reloadedAddon.name, ADDON_NAME,
"Add-on was reloaded: " + reloadedAddon.name);
yield onBootstrapInstallCalled;
info("Uninstall addon installed earlier.");
yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
const onUninstalled = promiseAddonEvent("onUninstalled");
reloadedAddon.uninstall();
const [uninstalledAddon] = yield onUninstalled;
is(uninstalledAddon.id, ADDON_ID,
"Add-on was uninstalled: " + uninstalledAddon.id);
yield closeAboutDebugging(tab);
});

Просмотреть файл

@ -3899,6 +3899,7 @@ this.XPIProvider = {
throw new Error("Only restartless (bootstrap) add-ons"
+ " can be temporarily installed:", addon.id);
}
let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
let oldAddon = yield new Promise(
resolve => XPIDatabase.getVisibleAddonForID(addon.id, resolve));
if (oldAddon) {
@ -3918,9 +3919,12 @@ this.XPIProvider = {
// call its uninstall method
let newVersion = addon.version;
let oldVersion = oldAddon.version;
let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
if (Services.vc.compare(newVersion, oldVersion) >= 0) {
installReason = BOOTSTRAP_REASONS.ADDON_UPGRADE;
} else {
installReason = BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
}
let uninstallReason = installReason;
if (oldAddon.active) {
XPIProvider.callBootstrapMethod(oldAddon, existingAddon,
@ -3937,8 +3941,7 @@ this.XPIProvider = {
let file = addon._sourceBundle;
XPIProvider._addURIMapping(addon.id, file);
XPIProvider.callBootstrapMethod(addon, file, "install",
BOOTSTRAP_REASONS.ADDON_INSTALL);
XPIProvider.callBootstrapMethod(addon, file, "install", installReason);
addon.state = AddonManager.STATE_INSTALLED;
logger.debug("Install of temporary addon in " + aFile.path + " completed.");
addon.visible = true;
@ -6968,6 +6971,10 @@ AddonWrapper.prototype = {
return getExternalType(addonFor(this).type);
},
get temporarilyInstalled() {
return addonFor(this)._installLocation == TemporaryInstallLocation;
},
get aboutURL() {
return this.isActive ? addonFor(this)["aboutURL"] : null;
},
@ -7344,25 +7351,26 @@ AddonWrapper.prototype = {
}
},
/**
* Reloads the add-on as if one had uninstalled it then reinstalled it.
*
* Currently, only temporarily installed add-ons can be reloaded. Attempting
* to reload other kinds of add-ons will result in a rejected promise.
*
* @return Promise
*/
reload: function() {
return new Promise(resolve => {
if (this.appDisabled) {
throw new Error(
"cannot reload add-on because it is disabled by the application");
}
return new Promise((resolve) => {
const addon = addonFor(this);
const isReloadable = (!XPIProvider.enableRequiresRestart(addon) &&
!XPIProvider.disableRequiresRestart(addon));
if (!isReloadable) {
throw new Error(
"cannot reload add-on because it requires a browser restart");
if (!this.temporarilyInstalled) {
logger.debug(`Cannot reload add-on at ${addon._sourceBundle}`);
throw new Error("Only temporary add-ons can be reloaded");
}
this.userDisabled = true;
flushChromeCaches();
this.userDisabled = false;
resolve();
logger.debug(`reloading add-on ${addon.id}`);
// This function supports re-installing an existing add-on.
resolve(AddonManager.installTemporaryAddon(addon._sourceBundle));
});
},

Просмотреть файл

@ -1,21 +0,0 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>bug1261522-app-disabled@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:targetApplication>
<Description>
<em:minVersion>1</em:minVersion>
<em:maxVersion>2</em:maxVersion>
<em:strictCompatibility>true</em:strictCompatibility>
</Description>
</em:targetApplication>
<em:name>An add-on that will be app-disabled when installed</em:name>
</Description>
</RDF>

Просмотреть файл

@ -178,10 +178,12 @@ this.BootstrapMonitor = {
},
checkAddonInstalled(id, version = undefined) {
let installed = this.installed.get(id);
do_check_neq(installed, undefined);
if (version != undefined)
do_check_eq(installed.data.version, version);
const installed = this.installed.get(id);
notEqual(installed, undefined);
if (version !== undefined) {
equal(installed.data.version, version);
}
return installed;
},
checkAddonNotInstalled(id) {

Просмотреть файл

@ -3,11 +3,25 @@
*/
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
startupManager();
function* getTestAddon(fixtureName, addonID) {
const sampleAddon = {
id: "webextension1@tests.mozilla.org",
name: "webextension_1",
}
const manifestSample = {
id: "bootstrap1@tests.mozilla.org",
version: "1.0",
bootstrap: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}],
};
function* installAddon(fixtureName, addonID) {
yield promiseInstallAllFiles([do_get_addon(fixtureName)]);
restartManager();
return promiseAddonByID(addonID);
}
@ -16,59 +30,153 @@ function* tearDownAddon(addon) {
yield promiseShutdownManager();
}
add_task(function* test_reloading_an_addon() {
const addonId = "webextension1@tests.mozilla.org";
const addon = yield getTestAddon("webextension_1", addonId);
add_task(function* test_reloading_a_temp_addon() {
yield promiseRestartManager();
yield AddonManager.installTemporaryAddon(do_get_addon(sampleAddon.name));
const addon = yield promiseAddonByID(sampleAddon.id)
const onDisabled = promiseAddonEvent("onDisabled");
const onEnabled = promiseAddonEvent("onEnabled");
var receivedOnUninstalled = false;
var receivedOnUninstalling = false;
var receivedOnInstalled = false;
var receivedOnInstalling = false;
const onReload = new Promise(resolve => {
const listener = {
onUninstalling: (addon) => {
if (addon.id === sampleAddon.id) {
receivedOnUninstalling = true;
}
},
onUninstalled: (addon) => {
if (addon.id === sampleAddon.id) {
receivedOnUninstalled = true;
}
},
onInstalling: (addon) => {
receivedOnInstalling = true;
equal(addon.id, sampleAddon.id);
},
onInstalled: (addon) => {
receivedOnInstalled = true;
equal(addon.id, sampleAddon.id);
// This should be the last event called.
AddonManager.removeAddonListener(listener);
resolve();
},
}
AddonManager.addAddonListener(listener);
});
yield addon.reload();
yield onReload;
// Make sure reload() doesn't trigger uninstall events.
equal(receivedOnUninstalled, false, "reload should not trigger onUninstalled");
equal(receivedOnUninstalling, false, "reload should not trigger onUninstalling");
// Make sure reload() triggers install events, like an upgrade.
equal(receivedOnInstalling, true, "reload should trigger onInstalling");
equal(receivedOnInstalled, true, "reload should trigger onInstalled");
yield tearDownAddon(addon);
});
add_task(function* test_cannot_reload_permanent_addon() {
yield promiseRestartManager();
const addon = yield installAddon(sampleAddon.name, sampleAddon.id);
yield Assert.rejects(addon.reload(), /Only temporary add-ons can be reloaded/);
yield tearDownAddon(addon);
});
add_task(function* test_disabled_addon_can_be_enabled_after_reload() {
yield promiseRestartManager();
let tempdir = gTmpD.clone();
// Create an add-on with strictCompatibility which should cause it
// to be appDisabled.
const unpackedAddon = writeInstallRDFToDir(
Object.assign({}, manifestSample, {
strictCompatibility: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "0.1",
maxVersion: "0.1"
}],
}), tempdir, manifestSample.id, "bootstrap.js");
yield AddonManager.installTemporaryAddon(unpackedAddon);
const addon = yield promiseAddonByID(manifestSample.id);
notEqual(addon, null);
equal(addon.appDisabled, true);
// Remove strictCompatibility from the manifest.
writeInstallRDFToDir(manifestSample, tempdir, manifestSample.id);
yield addon.reload();
const [disabledAddon] = yield onDisabled;
do_check_eq(disabledAddon.id, addonId);
const reloadedAddon = yield promiseAddonByID(manifestSample.id);
notEqual(reloadedAddon, null);
equal(reloadedAddon.appDisabled, false);
const [enabledAddon] = yield onEnabled;
do_check_eq(enabledAddon.id, addonId);
tearDownAddon(addon);
yield tearDownAddon(reloadedAddon);
unpackedAddon.remove(true);
});
add_task(function* test_cannot_reload_addon_requiring_restart() {
// This is a plain install.rdf add-on without a bootstrap script.
const addon = yield getTestAddon("test_install1", "addon1@tests.mozilla.org");
add_task(function* test_manifest_changes_are_refreshed() {
yield promiseRestartManager();
let tempdir = gTmpD.clone();
let caughtError = null;
try {
yield addon.reload();
} catch (error) {
caughtError = error;
}
const unpackedAddon = writeInstallRDFToDir(
Object.assign({}, manifestSample, {
name: "Test Bootstrap 1",
}), tempdir, manifestSample.id, "bootstrap.js");
do_check_eq(
caughtError && caughtError.message,
"cannot reload add-on because it requires a browser restart");
yield AddonManager.installTemporaryAddon(unpackedAddon);
const addon = yield promiseAddonByID(manifestSample.id);
notEqual(addon, null);
equal(addon.name, "Test Bootstrap 1");
tearDownAddon(addon);
writeInstallRDFToDir(Object.assign({}, manifestSample, {
name: "Test Bootstrap 1 (reloaded)",
}), tempdir, manifestSample.id);
yield addon.reload();
const reloadedAddon = yield promiseAddonByID(manifestSample.id);
notEqual(reloadedAddon, null);
equal(reloadedAddon.name, "Test Bootstrap 1 (reloaded)");
yield tearDownAddon(reloadedAddon);
unpackedAddon.remove(true);
});
add_task(function* test_cannot_reload_app_disabled_addon() {
// This add-on will be app disabled immediately.
const addon = yield getTestAddon(
"test_bug1261522_app_disabled",
"bug1261522-app-disabled@tests.mozilla.org");
do_check_eq(addon.appDisabled, true);
add_task(function* test_reload_fails_on_installation_errors() {
yield promiseRestartManager();
let tempdir = gTmpD.clone();
let caughtError = null;
try {
yield addon.reload();
} catch (error) {
caughtError = error;
}
const unpackedAddon = writeInstallRDFToDir(
Object.assign({}, manifestSample, {
name: "Test Bootstrap 1",
}), tempdir, manifestSample.id, "bootstrap.js");
do_check_eq(
caughtError && caughtError.message,
"cannot reload add-on because it is disabled by the application");
yield AddonManager.installTemporaryAddon(unpackedAddon);
const addon = yield promiseAddonByID(manifestSample.id);
notEqual(addon, null);
tearDownAddon(addon);
// Trigger an installation error with an empty manifest.
writeInstallRDFToDir({}, tempdir, manifestSample.id);
yield Assert.rejects(addon.reload(), /No ID in install manifest/);
// The old add-on should be active. I.E. the broken reload will not
// disturb it.
const oldAddon = yield promiseAddonByID(manifestSample.id);
notEqual(oldAddon, null);
equal(oldAddon.isActive, true);
equal(oldAddon.name, "Test Bootstrap 1");
yield tearDownAddon(addon);
unpackedAddon.remove(true);
});

Просмотреть файл

@ -3,12 +3,49 @@
*/
const ID = "bootstrap1@tests.mozilla.org";
const sampleRDFManifest = {
id: ID,
version: "1.0",
bootstrap: true,
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "1",
maxVersion: "1"
}],
name: "Test Bootstrap 1 (temporary)",
};
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
startupManager();
BootstrapMonitor.init();
// Partial list of bootstrap reasons from XPIProvider.jsm
const BOOTSTRAP_REASONS = {
ADDON_INSTALL: 5,
ADDON_UPGRADE: 7,
ADDON_DOWNGRADE: 8,
};
function waitForBootstrapEvent(expectedEvent, addonId) {
return new Promise(resolve => {
const observer = {
observe: (subject, topic, data) => {
const info = JSON.parse(data);
const targetAddonId = info.data.id;
if (targetAddonId === addonId && info.event === expectedEvent) {
resolve(info);
Services.obs.removeObserver(observer);
} else {
do_print(
`Ignoring bootstrap event: ${info.event} for ${targetAddonId}`);
}
},
};
Services.obs.addObserver(observer, "bootstrapmonitor-event", false);
});
}
// Install a temporary add-on with no existing add-on present.
// Restart and make sure it has gone away.
add_task(function*() {
@ -45,7 +82,8 @@ add_task(function*() {
do_check_true(installingCalled);
do_check_true(installedCalled);
BootstrapMonitor.checkAddonInstalled(ID, "1.0");
const install = BootstrapMonitor.checkAddonInstalled(ID, "1.0");
equal(install.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
BootstrapMonitor.checkAddonStarted(ID, "1.0");
let addon = yield promiseAddonByID(ID);
@ -254,6 +292,126 @@ add_task(function*() {
yield promiseRestartManager();
});
// Install a temporary add-on as a version upgrade over the top of an
// existing temporary add-on.
add_task(function*() {
const tempdir = gTmpD.clone();
writeInstallRDFToDir(sampleRDFManifest, tempdir,
"bootstrap1@tests.mozilla.org", "bootstrap.js");
const unpackedAddon = tempdir.clone();
unpackedAddon.append(ID);
do_get_file("data/test_temporary/bootstrap.js")
.copyTo(unpackedAddon, "bootstrap.js");
yield AddonManager.installTemporaryAddon(unpackedAddon);
// Increment the version number, re-install it, and make sure it
// gets marked as an upgrade.
writeInstallRDFToDir(Object.assign({}, sampleRDFManifest, {
version: "2.0"
}), tempdir, "bootstrap1@tests.mozilla.org");
const onUninstall = waitForBootstrapEvent("uninstall", ID);
const onInstall = waitForBootstrapEvent("install", ID);
yield AddonManager.installTemporaryAddon(unpackedAddon);
const uninstall = yield onUninstall;
equal(uninstall.data.version, "1.0");
equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
const install = yield onInstall;
equal(install.data.version, "2.0");
equal(install.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
const addon = yield promiseAddonByID(ID);
addon.uninstall();
unpackedAddon.remove(true);
yield promiseRestartManager();
});
// Install a temporary add-on as a version downgrade over the top of an
// existing temporary add-on.
add_task(function*() {
const tempdir = gTmpD.clone();
writeInstallRDFToDir(sampleRDFManifest, tempdir,
"bootstrap1@tests.mozilla.org", "bootstrap.js");
const unpackedAddon = tempdir.clone();
unpackedAddon.append(ID);
do_get_file("data/test_temporary/bootstrap.js")
.copyTo(unpackedAddon, "bootstrap.js");
yield AddonManager.installTemporaryAddon(unpackedAddon);
// Decrement the version number, re-install, and make sure
// it gets marked as a downgrade.
writeInstallRDFToDir(Object.assign({}, sampleRDFManifest, {
version: "0.8"
}), tempdir, "bootstrap1@tests.mozilla.org");
const onUninstall = waitForBootstrapEvent("uninstall", ID);
const onInstall = waitForBootstrapEvent("install", ID);
yield AddonManager.installTemporaryAddon(unpackedAddon);
const uninstall = yield onUninstall;
equal(uninstall.data.version, "1.0");
equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_DOWNGRADE);
const install = yield onInstall;
equal(install.data.version, "0.8");
equal(install.reason, BOOTSTRAP_REASONS.ADDON_DOWNGRADE);
const addon = yield promiseAddonByID(ID);
addon.uninstall();
unpackedAddon.remove(true);
yield promiseRestartManager();
});
// Installing a temporary add-on over an existing add-on with the same
// version number should be installed as an upgrade.
add_task(function*() {
const tempdir = gTmpD.clone();
writeInstallRDFToDir(sampleRDFManifest, tempdir,
"bootstrap1@tests.mozilla.org", "bootstrap.js");
const unpackedAddon = tempdir.clone();
unpackedAddon.append(ID);
do_get_file("data/test_temporary/bootstrap.js")
.copyTo(unpackedAddon, "bootstrap.js");
const onInitialInstall = waitForBootstrapEvent("install", ID);
yield AddonManager.installTemporaryAddon(unpackedAddon);
const initialInstall = yield onInitialInstall;
equal(initialInstall.data.version, "1.0");
equal(initialInstall.reason, BOOTSTRAP_REASONS.ADDON_INSTALL);
// Install it again.
const onUninstall = waitForBootstrapEvent("uninstall", ID);
const onInstall = waitForBootstrapEvent("install", ID);
yield AddonManager.installTemporaryAddon(unpackedAddon);
const uninstall = yield onUninstall;
equal(uninstall.data.version, "1.0");
equal(uninstall.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
const reInstall = yield onInstall;
equal(reInstall.data.version, "1.0");
equal(reInstall.reason, BOOTSTRAP_REASONS.ADDON_UPGRADE);
const addon = yield promiseAddonByID(ID);
addon.uninstall();
unpackedAddon.remove(true);
yield promiseRestartManager();
});
// Install a temporary add-on over the top of an existing disabled add-on.
// After restart, the existing add-on should continue to be installed and disabled.
add_task(function*() {
@ -435,3 +593,25 @@ add_task(function*() {
BootstrapMonitor.checkAddonNotInstalled(ID);
BootstrapMonitor.checkAddonNotStarted(ID);
});
// Check that a temporary add-on is marked as such.
add_task(function*() {
yield AddonManager.installTemporaryAddon(do_get_addon("test_bootstrap1_1"));
const addon = yield promiseAddonByID(ID);
notEqual(addon, null);
equal(addon.temporarilyInstalled, true);
yield promiseRestartManager();
});
// Check that a permanent add-on is not marked as temporarily installed.
add_task(function*() {
yield promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
const addon = yield promiseAddonByID(ID);
notEqual(addon, null);
equal(addon.temporarilyInstalled, false);
yield promiseRestartManager();
});