From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../extensions/test/xpcshell/head_system_addons.js | 472 +++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js (limited to 'toolkit/mozapps/extensions/test/xpcshell/head_system_addons.js') 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 = `\n\n\n`; + if (addons) { + xml += ` \n`; + for (let addon of addons) { + if (addon.xpi) { + testserver.registerFile(`/data/${addon.path}`, addon.xpi); + } + + xml += ` \n`; + } + xml += ` \n`; + } + xml += `\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} 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} 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} initialState - an array of objects of the form {isUpgrade: false, version: null} + * @param {Array} 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} 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} 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(); +} -- cgit v1.2.3