summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js472
1 files changed, 472 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js
new file mode 100644
index 0000000000..4cb591dd56
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js
@@ -0,0 +1,472 @@
+/* import-globals-from head_addons.js */
+
+const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
+const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url";
+const PREF_SYSTEM_ADDON_UPDATE_ENABLED =
+ "extensions.systemAddon.update.enabled";
+
+// See bug 1507255
+Services.prefs.setBoolPref("media.gmp-manager.updateEnabled", true);
+
+function root(server) {
+ let { primaryScheme, primaryHost, primaryPort } = server.identity;
+ return `${primaryScheme}://${primaryHost}:${primaryPort}/data`;
+}
+
+XPCOMUtils.defineLazyGetter(this, "testserver", () => {
+ let server = new HttpServer();
+ server.start();
+ Services.prefs.setCharPref(
+ PREF_SYSTEM_ADDON_UPDATE_URL,
+ `${root(server)}/update.xml`
+ );
+ return server;
+});
+
+async function serveSystemUpdate(xml, perform_update) {
+ testserver.registerPathHandler("/data/update.xml", (request, response) => {
+ response.write(xml);
+ });
+
+ try {
+ await perform_update();
+ } finally {
+ testserver.registerPathHandler("/data/update.xml", null);
+ }
+}
+
+// Runs an update check making it use the passed in xml string. Uses the direct
+// call to the update function so we get rejections on failure.
+async function installSystemAddons(xml, waitIDs = []) {
+ info("Triggering system add-on update check.");
+
+ await serveSystemUpdate(
+ xml,
+ async function () {
+ let { XPIProvider } = ChromeUtils.import(
+ "resource://gre/modules/addons/XPIProvider.jsm"
+ );
+ await Promise.all([
+ XPIProvider.updateSystemAddons(),
+ ...waitIDs.map(id => promiseWebExtensionStartup(id)),
+ ]);
+ },
+ testserver
+ );
+}
+
+// Runs a full add-on update check which will in some cases do a system add-on
+// update check. Always succeeds.
+async function updateAllSystemAddons(xml) {
+ info("Triggering full add-on update check.");
+
+ await serveSystemUpdate(
+ xml,
+ function () {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(
+ observer,
+ "addons-background-update-complete"
+ );
+
+ resolve();
+ }, "addons-background-update-complete");
+
+ // Trigger the background update timer handler
+ gInternalManager.notify(null);
+ });
+ },
+ testserver
+ );
+}
+
+// Builds an update.xml file for an update check based on the data passed.
+function buildSystemAddonUpdates(addons) {
+ let xml = `<?xml version="1.0" encoding="UTF-8"?>\n\n<updates>\n`;
+ if (addons) {
+ xml += ` <addons>\n`;
+ for (let addon of addons) {
+ if (addon.xpi) {
+ testserver.registerFile(`/data/${addon.path}`, addon.xpi);
+ }
+
+ xml += ` <addon id="${addon.id}" URL="${root(testserver)}/${
+ addon.path
+ }" version="${addon.version}"`;
+ if (addon.hashFunction) {
+ xml += ` hashFunction="${addon.hashFunction}"`;
+ }
+ if (addon.hashValue) {
+ xml += ` hashValue="${addon.hashValue}"`;
+ }
+ xml += `/>\n`;
+ }
+ xml += ` </addons>\n`;
+ }
+ xml += `</updates>\n`;
+
+ return xml;
+}
+
+let _systemXPIs = new Map();
+function getSystemAddonXPI(num, version) {
+ let key = `${num}:${version}`;
+ if (!_systemXPIs.has(key)) {
+ _systemXPIs.set(
+ key,
+ AddonTestUtils.createTempWebExtensionFile({
+ manifest: {
+ name: `System Add-on ${num}`,
+ version,
+ browser_specific_settings: {
+ gecko: {
+ id: `system${num}@tests.mozilla.org`,
+ },
+ },
+ },
+ })
+ );
+ }
+ return _systemXPIs.get(key);
+}
+
+async function initSystemAddonDirs() {
+ let hiddenSystemAddonDir = FileUtils.getDir(
+ "ProfD",
+ ["sysfeatures", "hidden"],
+ true
+ );
+ let system1_1 = await getSystemAddonXPI(1, "1.0");
+ system1_1.copyTo(hiddenSystemAddonDir, "system1@tests.mozilla.org.xpi");
+
+ let system2_1 = await getSystemAddonXPI(2, "1.0");
+ system2_1.copyTo(hiddenSystemAddonDir, "system2@tests.mozilla.org.xpi");
+
+ let prefilledSystemAddonDir = FileUtils.getDir(
+ "ProfD",
+ ["sysfeatures", "prefilled"],
+ true
+ );
+ let system2_2 = await getSystemAddonXPI(2, "2.0");
+ system2_2.copyTo(prefilledSystemAddonDir, "system2@tests.mozilla.org.xpi");
+ let system3_2 = await getSystemAddonXPI(3, "2.0");
+ system3_2.copyTo(prefilledSystemAddonDir, "system3@tests.mozilla.org.xpi");
+}
+
+/**
+ * Returns current system add-on update directory (stored in pref).
+ */
+function getCurrentSystemAddonUpdatesDir() {
+ const updatesDir = FileUtils.getDir("ProfD", ["features"], false);
+ let dir = updatesDir.clone();
+ let set = JSON.parse(Services.prefs.getCharPref(PREF_SYSTEM_ADDON_SET));
+ dir.append(set.directory);
+ return dir;
+}
+
+/**
+ * Removes all files from system add-on update directory.
+ */
+function clearSystemAddonUpdatesDir() {
+ const updatesDir = FileUtils.getDir("ProfD", ["features"], false);
+ // Delete any existing directories
+ if (updatesDir.exists()) {
+ updatesDir.remove(true);
+ }
+
+ Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET);
+}
+
+registerCleanupFunction(() => {
+ clearSystemAddonUpdatesDir();
+});
+
+/**
+ * Installs a known set of add-ons into the system add-on update directory.
+ */
+async function buildPrefilledUpdatesDir() {
+ clearSystemAddonUpdatesDir();
+
+ // Build the test set
+ let dir = FileUtils.getDir("ProfD", ["features", "prefilled"], true);
+
+ let xpi = await getSystemAddonXPI(2, "2.0");
+ xpi.copyTo(dir, "system2@tests.mozilla.org.xpi");
+
+ xpi = await getSystemAddonXPI(3, "2.0");
+ xpi.copyTo(dir, "system3@tests.mozilla.org.xpi");
+
+ // Mark these in the past so the startup file scan notices when files have changed properly
+ FileUtils.getFile("ProfD", [
+ "features",
+ "prefilled",
+ "system2@tests.mozilla.org.xpi",
+ ]).lastModifiedTime -= 10000;
+ FileUtils.getFile("ProfD", [
+ "features",
+ "prefilled",
+ "system3@tests.mozilla.org.xpi",
+ ]).lastModifiedTime -= 10000;
+
+ Services.prefs.setCharPref(
+ PREF_SYSTEM_ADDON_SET,
+ JSON.stringify({
+ schema: 1,
+ directory: dir.leafName,
+ addons: {
+ "system2@tests.mozilla.org": {
+ version: "2.0",
+ },
+ "system3@tests.mozilla.org": {
+ version: "2.0",
+ },
+ },
+ })
+ );
+}
+
+/**
+ * Check currently installed ssystem add-ons against a set of conditions.
+ *
+ * @param {Array<Object>} conditions - an array of objects of the form { isUpgrade: false, version: null}
+ * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory)
+ */
+async function checkInstalledSystemAddons(conditions, distroDir) {
+ for (let i = 0; i < conditions.length; i++) {
+ let condition = conditions[i];
+ let id = "system" + (i + 1) + "@tests.mozilla.org";
+ let addon = await promiseAddonByID(id);
+
+ if (!("isUpgrade" in condition) || !("version" in condition)) {
+ throw Error("condition must contain isUpgrade and version");
+ }
+ let isUpgrade = conditions[i].isUpgrade;
+ let version = conditions[i].version;
+
+ let expectedDir = isUpgrade ? getCurrentSystemAddonUpdatesDir() : distroDir;
+
+ if (version) {
+ info(`Checking state of add-on ${id}, expecting version ${version}`);
+
+ // Add-on should be installed
+ Assert.notEqual(addon, null);
+ Assert.equal(addon.version, version);
+ Assert.ok(addon.isActive);
+ Assert.ok(!addon.foreignInstall);
+ Assert.ok(addon.hidden);
+ Assert.ok(addon.isSystem);
+
+ // Verify the add-ons file is in the right place
+ let file = expectedDir.clone();
+ file.append(id + ".xpi");
+ Assert.ok(file.exists());
+ Assert.ok(file.isFile());
+
+ let uri = addon.getResourceURI();
+ if (uri instanceof Ci.nsIJARURI) {
+ uri = uri.JARFile;
+ }
+
+ Assert.ok(uri instanceof Ci.nsIFileURL);
+ Assert.equal(uri.file.path, file.path);
+
+ if (isUpgrade) {
+ Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
+ }
+ } else {
+ info(`Checking state of add-on ${id}, expecting it to be missing`);
+
+ if (isUpgrade) {
+ // Add-on should not be installed
+ Assert.equal(addon, null);
+ }
+ }
+ }
+}
+
+/**
+ * Returns all system add-on updates directories.
+ */
+async function getSystemAddonDirectories() {
+ const updatesDir = FileUtils.getDir("ProfD", ["features"], false);
+ let subdirs = [];
+
+ if (await IOUtils.exists(updatesDir.path)) {
+ for (const child of await IOUtils.getChildren(updatesDir.path)) {
+ const stat = await IOUtils.stat(child);
+ if (stat.type === "directory") {
+ subdirs.push(child);
+ }
+ }
+ }
+
+ return subdirs;
+}
+
+/**
+ * Sets up initial system add-on update conditions.
+ *
+ * @param {Object<function, Array<Object>} setup - an object containing a setup function and an array of objects
+ * of the form {isUpgrade: false, version: null}
+ *
+ * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory)
+ */
+async function setupSystemAddonConditions(setup, distroDir) {
+ info("Clearing existing database.");
+ Services.prefs.clearUserPref(PREF_SYSTEM_ADDON_SET);
+ distroDir.leafName = "empty";
+
+ let updateList = [];
+ await overrideBuiltIns({ system: updateList });
+ await promiseStartupManager();
+ await promiseShutdownManager();
+
+ info("Setting up conditions.");
+ await setup.setup();
+
+ if (distroDir) {
+ if (distroDir.path.endsWith("hidden")) {
+ updateList = ["system1@tests.mozilla.org", "system2@tests.mozilla.org"];
+ } else if (distroDir.path.endsWith("prefilled")) {
+ updateList = ["system2@tests.mozilla.org", "system3@tests.mozilla.org"];
+ }
+ }
+ await overrideBuiltIns({ system: updateList });
+
+ let startupPromises = setup.initialState.map((item, i) =>
+ item.version
+ ? promiseWebExtensionStartup(`system${i + 1}@tests.mozilla.org`)
+ : null
+ );
+ await Promise.all([promiseStartupManager(), ...startupPromises]);
+
+ // Make sure the initial state is correct
+ info("Checking initial state.");
+ await checkInstalledSystemAddons(setup.initialState, distroDir);
+}
+
+/**
+ * Verify state of system add-ons after installation.
+ *
+ * @param {Array<Object>} initialState - an array of objects of the form {isUpgrade: false, version: null}
+ * @param {Array<Object>} finalState - an array of objects of the form {isUpgrade: false, version: null}
+ * @param {Boolean} alreadyUpgraded - whether a restartless upgrade has already been performed.
+ * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory)
+ */
+async function verifySystemAddonState(
+ initialState,
+ finalState = undefined,
+ alreadyUpgraded = false,
+ distroDir
+) {
+ let expectedDirs = 0;
+
+ // If the initial state was using the profile set then that directory will
+ // still exist.
+
+ if (initialState.some(a => a.isUpgrade)) {
+ expectedDirs++;
+ }
+
+ if (finalState == undefined) {
+ finalState = initialState;
+ } else if (finalState.some(a => a.isUpgrade)) {
+ // If the new state is using the profile then that directory will exist.
+ expectedDirs++;
+ }
+
+ // Since upgrades are restartless now, the previous update dir hasn't been removed.
+ if (alreadyUpgraded) {
+ expectedDirs++;
+ }
+
+ info("Checking final state.");
+
+ let dirs = await getSystemAddonDirectories();
+ Assert.equal(dirs.length, expectedDirs);
+
+ await checkInstalledSystemAddons(...finalState, distroDir);
+
+ // Check that the new state is active after a restart
+ await promiseShutdownManager();
+
+ let updateList = [];
+
+ if (distroDir) {
+ if (distroDir.path.endsWith("hidden")) {
+ updateList = ["system1@tests.mozilla.org", "system2@tests.mozilla.org"];
+ } else if (distroDir.path.endsWith("prefilled")) {
+ updateList = ["system2@tests.mozilla.org", "system3@tests.mozilla.org"];
+ }
+ }
+ await overrideBuiltIns({ system: updateList });
+ await promiseStartupManager();
+ await checkInstalledSystemAddons(finalState, distroDir);
+}
+
+/**
+ * Run system add-on tests and compare the results against a set of expected conditions.
+ *
+ * @param {String} setupName - name of the current setup conditions.
+ * @param {Object<function, Array<Object>} setup - Defines the set of initial conditions to run each test against. Each should
+ * define the following properties:
+ * setup: A task to setup the profile into the initial state.
+ * initialState: The initial expected system add-on state after setup has run.
+ * @param {Array<Object>} test - The test to run. Each test must define an updateList or test. The following
+ * properties are used:
+ * updateList: The set of add-ons the server should respond with.
+ * test: A function to run to perform the update check (replaces
+ * updateList)
+ * fails: An optional regex property, if present the update check is expected to
+ * fail.
+ * finalState: An optional property, the expected final state of system add-ons,
+ * if missing the test condition's initialState is used.
+ * @param {nsIFile} distroDir - the system add-on distribution directory (the "features" dir in the app directory)
+ */
+
+async function execSystemAddonTest(setupName, setup, test, distroDir) {
+ // Initial system addon conditions need system signature
+ AddonTestUtils.usePrivilegedSignatures = "system";
+ await setupSystemAddonConditions(setup, distroDir);
+
+ // The test may define what signature to use when running the test
+ if (test.usePrivilegedSignatures != undefined) {
+ AddonTestUtils.usePrivilegedSignatures = test.usePrivilegedSignatures;
+ }
+
+ function runTest() {
+ if ("test" in test) {
+ return test.test();
+ }
+ let xml = buildSystemAddonUpdates(test.updateList);
+ let ids = (test.updateList || []).map(item => item.id);
+ return installSystemAddons(xml, ids);
+ }
+
+ if (test.fails) {
+ await Assert.rejects(runTest(), test.fails);
+ } else {
+ await runTest();
+ }
+
+ // some tests have a different expected combination of default
+ // and updated add-ons.
+ if (test.finalState && setupName in test.finalState) {
+ await verifySystemAddonState(
+ setup.initialState,
+ test.finalState[setupName],
+ false,
+ distroDir
+ );
+ } else {
+ await verifySystemAddonState(
+ setup.initialState,
+ undefined,
+ false,
+ distroDir
+ );
+ }
+
+ await promiseShutdownManager();
+}