diff options
Diffstat (limited to 'toolkit/mozapps/webextensions/test/xpcshell/head_addons.js')
-rw-r--r-- | toolkit/mozapps/webextensions/test/xpcshell/head_addons.js | 1345 |
1 files changed, 0 insertions, 1345 deletions
diff --git a/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js b/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js deleted file mode 100644 index 960caceeb..000000000 --- a/toolkit/mozapps/webextensions/test/xpcshell/head_addons.js +++ /dev/null @@ -1,1345 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -var AM_Cc = Components.classes; -var AM_Ci = Components.interfaces; -var AM_Cu = Components.utils; - -AM_Cu.importGlobalProperties(["TextEncoder"]); - -const CERTDB_CONTRACTID = "@mozilla.org/security/x509certdb;1"; -const CERTDB_CID = Components.ID("{fb0bbc5c-452e-4783-b32c-80124693d871}"); - -const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity"; -const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility"; -const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"; -const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion"; -const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; -const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url"; -const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required"; - -// Forcibly end the test if it runs longer than 15 minutes -const TIMEOUT_MS = 900000; - -// Maximum error in file modification times. Some file systems don't store -// modification times exactly. As long as we are closer than this then it -// still passes. -const MAX_TIME_DIFFERENCE = 3000; - -// Time to reset file modified time relative to Date.now() so we can test that -// times are modified (10 hours old). -const MAKE_FILE_OLD_DIFFERENCE = 10 * 3600 * 1000; - -Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm"); -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/FileUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); -Components.utils.import("resource://gre/modules/NetUtil.jsm"); -Components.utils.import("resource://gre/modules/Promise.jsm"); -Components.utils.import("resource://gre/modules/Task.jsm"); -const { OS } = Components.utils.import("resource://gre/modules/osfile.jsm", {}); -Components.utils.import("resource://gre/modules/AsyncShutdown.jsm"); - -Components.utils.import("resource://testing-common/AddonTestUtils.jsm"); - -XPCOMUtils.defineLazyModuleGetter(this, "Extension", - "resource://gre/modules/Extension.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "ExtensionTestUtils", - "resource://testing-common/ExtensionXPCShellUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "HttpServer", - "resource://testing-common/httpd.js"); -XPCOMUtils.defineLazyModuleGetter(this, "MockAsyncShutdown", - "resource://testing-common/AddonTestUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MockRegistrar", - "resource://testing-common/MockRegistrar.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MockRegistry", - "resource://testing-common/MockRegistry.jsm"); - -const { - awaitPromise, - createAppInfo, - createInstallRDF, - createTempWebExtensionFile, - createUpdateRDF, - getFileForAddon, - manuallyInstall, - manuallyUninstall, - promiseAddonByID, - promiseAddonEvent, - promiseAddonsByIDs, - promiseAddonsWithOperationsByTypes, - promiseCompleteAllInstalls, - promiseConsoleOutput, - promiseFindAddonUpdates, - promiseInstallAllFiles, - promiseInstallFile, - promiseRestartManager, - promiseSetExtensionModifiedTime, - promiseShutdownManager, - promiseStartupManager, - promiseWriteProxyFileToDir, - registerDirectory, - setExtensionModifiedTime, - writeFilesToZip -} = AddonTestUtils; - -// WebExtension wrapper for ease of testing -ExtensionTestUtils.init(this); - -AddonTestUtils.init(this); -AddonTestUtils.overrideCertDB(); - -Object.defineProperty(this, "gAppInfo", { - get() { - return AddonTestUtils.appInfo; - }, -}); - -Object.defineProperty(this, "gExtensionsINI", { - get() { - return AddonTestUtils.extensionsINI.clone(); - }, -}); - -Object.defineProperty(this, "gInternalManager", { - get() { - return AddonTestUtils.addonIntegrationService.QueryInterface(AM_Ci.nsITimerCallback); - }, -}); - -Object.defineProperty(this, "gProfD", { - get() { - return AddonTestUtils.profileDir.clone(); - }, -}); - -Object.defineProperty(this, "gTmpD", { - get() { - return AddonTestUtils.tempDir.clone(); - }, -}); - -Object.defineProperty(this, "gUseRealCertChecks", { - get() { - return AddonTestUtils.useRealCertChecks; - }, - set(val) { - return AddonTestUtils.useRealCertChecks = val; - }, -}); - -Object.defineProperty(this, "TEST_UNPACKED", { - get() { - return AddonTestUtils.testUnpacked; - }, - set(val) { - return AddonTestUtils.testUnpacked = val; - }, -}); - -// We need some internal bits of AddonManager -var AMscope = Components.utils.import("resource://gre/modules/AddonManager.jsm", {}); -var { AddonManager, AddonManagerInternal, AddonManagerPrivate } = AMscope; - -var gPort = null; -var gUrlToFileMap = {}; - -// Map resource://xpcshell-data/ to the data directory -var resHandler = Services.io.getProtocolHandler("resource") - .QueryInterface(AM_Ci.nsISubstitutingProtocolHandler); -// Allow non-existent files because of bug 1207735 -var dataURI = NetUtil.newURI(do_get_file("data", true)); -resHandler.setSubstitution("xpcshell-data", dataURI); - -function isManifestRegistered(file) { - let manifests = Components.manager.getManifestLocations(); - for (let i = 0; i < manifests.length; i++) { - let manifest = manifests.queryElementAt(i, AM_Ci.nsIURI); - - // manifest is the url to the manifest file either in an XPI or a directory. - // We want the location of the XPI or directory itself. - if (manifest instanceof AM_Ci.nsIJARURI) { - manifest = manifest.JARFile.QueryInterface(AM_Ci.nsIFileURL).file; - } - else if (manifest instanceof AM_Ci.nsIFileURL) { - manifest = manifest.file.parent; - } - else { - continue; - } - - if (manifest.equals(file)) - return true; - } - return false; -} - -// Listens to messages from bootstrap.js telling us what add-ons were started -// and stopped etc. and performs some sanity checks that only installed add-ons -// are started etc. -this.BootstrapMonitor = { - inited: false, - - // Contain the current state of add-ons in the system - installed: new Map(), - started: new Map(), - - // Contain the last state of shutdown and uninstall calls for an add-on - stopped: new Map(), - uninstalled: new Map(), - - startupPromises: [], - installPromises: [], - - init() { - this.inited = true; - Services.obs.addObserver(this, "bootstrapmonitor-event", false); - }, - - shutdownCheck() { - if (!this.inited) - return; - - do_check_eq(this.started.size, 0); - }, - - clear(id) { - this.installed.delete(id); - this.started.delete(id); - this.stopped.delete(id); - this.uninstalled.delete(id); - }, - - promiseAddonStartup(id) { - return new Promise(resolve => { - this.startupPromises.push(resolve); - }); - }, - - promiseAddonInstall(id) { - return new Promise(resolve => { - this.installPromises.push(resolve); - }); - }, - - checkMatches(cached, current) { - do_check_neq(cached, undefined); - do_check_eq(current.data.version, cached.data.version); - do_check_eq(current.data.installPath, cached.data.installPath); - do_check_eq(current.data.resourceURI, cached.data.resourceURI); - }, - - checkAddonStarted(id, version = undefined) { - let started = this.started.get(id); - do_check_neq(started, undefined); - if (version != undefined) - do_check_eq(started.data.version, version); - - // Chrome should be registered by now - let installPath = new FileUtils.File(started.data.installPath); - let isRegistered = isManifestRegistered(installPath); - do_check_true(isRegistered); - }, - - checkAddonNotStarted(id) { - do_check_false(this.started.has(id)); - }, - - checkAddonInstalled(id, version = undefined) { - const installed = this.installed.get(id); - notEqual(installed, undefined); - if (version !== undefined) { - equal(installed.data.version, version); - } - return installed; - }, - - checkAddonNotInstalled(id) { - do_check_false(this.installed.has(id)); - }, - - observe(subject, topic, data) { - let info = JSON.parse(data); - let id = info.data.id; - let installPath = new FileUtils.File(info.data.installPath); - - if (subject && subject.wrappedJSObject) { - // NOTE: in some of the new tests, we need to received the real objects instead of - // their JSON representations, but most of the current tests expect intallPath - // and resourceURI to have been converted to strings. - info.data = Object.assign({}, subject.wrappedJSObject.data, { - installPath: info.data.installPath, - resourceURI: info.data.resourceURI, - }); - } - - // If this is the install event the add-ons shouldn't already be installed - if (info.event == "install") { - this.checkAddonNotInstalled(id); - - this.installed.set(id, info); - - for (let resolve of this.installPromises) - resolve(); - this.installPromises = []; - } - else { - this.checkMatches(this.installed.get(id), info); - } - - // If this is the shutdown event than the add-on should already be started - if (info.event == "shutdown") { - this.checkMatches(this.started.get(id), info); - - this.started.delete(id); - this.stopped.set(id, info); - - // Chrome should still be registered at this point - let isRegistered = isManifestRegistered(installPath); - do_check_true(isRegistered); - - // XPIProvider doesn't bother unregistering chrome on app shutdown but - // since we simulate restarts we must do so manually to keep the registry - // consistent. - if (info.reason == 2 /* APP_SHUTDOWN */) - Components.manager.removeBootstrappedManifestLocation(installPath); - } - else { - this.checkAddonNotStarted(id); - } - - if (info.event == "uninstall") { - // Chrome should be unregistered at this point - let isRegistered = isManifestRegistered(installPath); - do_check_false(isRegistered); - - this.installed.delete(id); - this.uninstalled.set(id, info) - } - else if (info.event == "startup") { - this.started.set(id, info); - - // Chrome should be registered at this point - let isRegistered = isManifestRegistered(installPath); - do_check_true(isRegistered); - - for (let resolve of this.startupPromises) - resolve(); - this.startupPromises = []; - } - } -} - -AddonTestUtils.on("addon-manager-shutdown", () => BootstrapMonitor.shutdownCheck()); - -function isNightlyChannel() { - var channel = "default"; - try { - channel = Services.prefs.getCharPref("app.update.channel"); - } - catch (e) { } - - return channel != "aurora" && channel != "beta" && channel != "release" && channel != "esr"; -} - -/** - * Tests that an add-on does appear in the crash report annotations, if - * crash reporting is enabled. The test will fail if the add-on is not in the - * annotation. - * @param aId - * The ID of the add-on - * @param aVersion - * The version of the add-on - */ -function do_check_in_crash_annotation(aId, aVersion) { - if (!("nsICrashReporter" in AM_Ci)) - return; - - if (!("Add-ons" in gAppInfo.annotations)) { - do_check_false(true); - return; - } - - let addons = gAppInfo.annotations["Add-ons"].split(","); - do_check_false(addons.indexOf(encodeURIComponent(aId) + ":" + - encodeURIComponent(aVersion)) < 0); -} - -/** - * Tests that an add-on does not appear in the crash report annotations, if - * crash reporting is enabled. The test will fail if the add-on is in the - * annotation. - * @param aId - * The ID of the add-on - * @param aVersion - * The version of the add-on - */ -function do_check_not_in_crash_annotation(aId, aVersion) { - if (!("nsICrashReporter" in AM_Ci)) - return; - - if (!("Add-ons" in gAppInfo.annotations)) { - do_check_true(true); - return; - } - - let addons = gAppInfo.annotations["Add-ons"].split(","); - do_check_true(addons.indexOf(encodeURIComponent(aId) + ":" + - encodeURIComponent(aVersion)) < 0); -} - -/** - * Returns a testcase xpi - * - * @param aName - * The name of the testcase (without extension) - * @return an nsIFile pointing to the testcase xpi - */ -function do_get_addon(aName) { - return do_get_file("addons/" + aName + ".xpi"); -} - -function do_get_addon_hash(aName, aAlgorithm) { - let file = do_get_addon(aName); - return do_get_file_hash(file); -} - -function do_get_file_hash(aFile, aAlgorithm) { - if (!aAlgorithm) - aAlgorithm = "sha1"; - - let crypto = AM_Cc["@mozilla.org/security/hash;1"]. - createInstance(AM_Ci.nsICryptoHash); - crypto.initWithString(aAlgorithm); - let fis = AM_Cc["@mozilla.org/network/file-input-stream;1"]. - createInstance(AM_Ci.nsIFileInputStream); - fis.init(aFile, -1, -1, false); - crypto.updateFromStream(fis, aFile.fileSize); - - // return the two-digit hexadecimal code for a byte - let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2); - - let binary = crypto.finish(false); - let hash = Array.from(binary, c => toHexString(c.charCodeAt(0))); - return aAlgorithm + ":" + hash.join(""); -} - -/** - * Returns an extension uri spec - * - * @param aProfileDir - * The extension install directory - * @return a uri spec pointing to the root of the extension - */ -function do_get_addon_root_uri(aProfileDir, aId) { - let path = aProfileDir.clone(); - path.append(aId); - if (!path.exists()) { - path.leafName += ".xpi"; - return "jar:" + Services.io.newFileURI(path).spec + "!/"; - } - return Services.io.newFileURI(path).spec; -} - -function do_get_expected_addon_name(aId) { - if (TEST_UNPACKED) - return aId; - return aId + ".xpi"; -} - -/** - * Check that an array of actual add-ons is the same as an array of - * expected add-ons. - * - * @param aActualAddons - * The array of actual add-ons to check. - * @param aExpectedAddons - * The array of expected add-ons to check against. - * @param aProperties - * An array of properties to check. - */ -function do_check_addons(aActualAddons, aExpectedAddons, aProperties) { - do_check_neq(aActualAddons, null); - do_check_eq(aActualAddons.length, aExpectedAddons.length); - for (let i = 0; i < aActualAddons.length; i++) - do_check_addon(aActualAddons[i], aExpectedAddons[i], aProperties); -} - -/** - * Check that the actual add-on is the same as the expected add-on. - * - * @param aActualAddon - * The actual add-on to check. - * @param aExpectedAddon - * The expected add-on to check against. - * @param aProperties - * An array of properties to check. - */ -function do_check_addon(aActualAddon, aExpectedAddon, aProperties) { - do_check_neq(aActualAddon, null); - - aProperties.forEach(function(aProperty) { - let actualValue = aActualAddon[aProperty]; - let expectedValue = aExpectedAddon[aProperty]; - - // Check that all undefined expected properties are null on actual add-on - if (!(aProperty in aExpectedAddon)) { - if (actualValue !== undefined && actualValue !== null) { - do_throw("Unexpected defined/non-null property for add-on " + - aExpectedAddon.id + " (addon[" + aProperty + "] = " + - actualValue.toSource() + ")"); - } - - return; - } - else if (expectedValue && !actualValue) { - do_throw("Missing property for add-on " + aExpectedAddon.id + - ": expected addon[" + aProperty + "] = " + expectedValue); - return; - } - - switch (aProperty) { - case "creator": - do_check_author(actualValue, expectedValue); - break; - - case "developers": - case "translators": - case "contributors": - do_check_eq(actualValue.length, expectedValue.length); - for (let i = 0; i < actualValue.length; i++) - do_check_author(actualValue[i], expectedValue[i]); - break; - - case "screenshots": - do_check_eq(actualValue.length, expectedValue.length); - for (let i = 0; i < actualValue.length; i++) - do_check_screenshot(actualValue[i], expectedValue[i]); - break; - - case "sourceURI": - do_check_eq(actualValue.spec, expectedValue); - break; - - case "updateDate": - do_check_eq(actualValue.getTime(), expectedValue.getTime()); - break; - - case "compatibilityOverrides": - do_check_eq(actualValue.length, expectedValue.length); - for (let i = 0; i < actualValue.length; i++) - do_check_compatibilityoverride(actualValue[i], expectedValue[i]); - break; - - case "icons": - do_check_icons(actualValue, expectedValue); - break; - - default: - if (remove_port(actualValue) !== remove_port(expectedValue)) - do_throw("Failed for " + aProperty + " for add-on " + aExpectedAddon.id + - " (" + actualValue + " === " + expectedValue + ")"); - } - }); -} - -/** - * Check that the actual author is the same as the expected author. - * - * @param aActual - * The actual author to check. - * @param aExpected - * The expected author to check against. - */ -function do_check_author(aActual, aExpected) { - do_check_eq(aActual.toString(), aExpected.name); - do_check_eq(aActual.name, aExpected.name); - do_check_eq(aActual.url, aExpected.url); -} - -/** - * Check that the actual screenshot is the same as the expected screenshot. - * - * @param aActual - * The actual screenshot to check. - * @param aExpected - * The expected screenshot to check against. - */ -function do_check_screenshot(aActual, aExpected) { - do_check_eq(aActual.toString(), aExpected.url); - do_check_eq(aActual.url, aExpected.url); - do_check_eq(aActual.width, aExpected.width); - do_check_eq(aActual.height, aExpected.height); - do_check_eq(aActual.thumbnailURL, aExpected.thumbnailURL); - do_check_eq(aActual.thumbnailWidth, aExpected.thumbnailWidth); - do_check_eq(aActual.thumbnailHeight, aExpected.thumbnailHeight); - do_check_eq(aActual.caption, aExpected.caption); -} - -/** - * Check that the actual compatibility override is the same as the expected - * compatibility override. - * - * @param aAction - * The actual compatibility override to check. - * @param aExpected - * The expected compatibility override to check against. - */ -function do_check_compatibilityoverride(aActual, aExpected) { - do_check_eq(aActual.type, aExpected.type); - do_check_eq(aActual.minVersion, aExpected.minVersion); - do_check_eq(aActual.maxVersion, aExpected.maxVersion); - do_check_eq(aActual.appID, aExpected.appID); - do_check_eq(aActual.appMinVersion, aExpected.appMinVersion); - do_check_eq(aActual.appMaxVersion, aExpected.appMaxVersion); -} - -function do_check_icons(aActual, aExpected) { - for (var size in aExpected) { - do_check_eq(remove_port(aActual[size]), remove_port(aExpected[size])); - } -} - -function startupManager(aAppChanged) { - promiseStartupManager(aAppChanged); -} - -/** - * Restarts the add-on manager as if the host application was restarted. - * - * @param aNewVersion - * An optional new version to use for the application. Passing this - * will change nsIXULAppInfo.version and make the startup appear as if - * the application version has changed. - */ -function restartManager(aNewVersion) { - awaitPromise(promiseRestartManager(aNewVersion)); -} - -function shutdownManager() { - awaitPromise(promiseShutdownManager()); -} - -function isItemMarkedMPIncompatible(aId) { - return AddonTestUtils.addonsList.isMultiprocessIncompatible(aId); -} - -function isThemeInAddonsList(aDir, aId) { - return AddonTestUtils.addonsList.hasTheme(aDir, aId); -} - -function isExtensionInAddonsList(aDir, aId) { - return AddonTestUtils.addonsList.hasExtension(aDir, aId); -} - -function check_startup_changes(aType, aIds) { - var ids = aIds.slice(0); - ids.sort(); - var changes = AddonManager.getStartupChanges(aType); - changes = changes.filter(aEl => /@tests.mozilla.org$/.test(aEl)); - changes.sort(); - - do_check_eq(JSON.stringify(ids), JSON.stringify(changes)); -} - -/** - * Writes an install.rdf manifest into a directory using the properties passed - * in a JS object. The objects should contain a property for each property to - * appear in the RDF. The object may contain an array of objects with id, - * minVersion and maxVersion in the targetApplications property to give target - * application compatibility. - * - * @param aData - * The object holding data about the add-on - * @param aDir - * The directory to add the install.rdf to - * @param aId - * An optional string to override the default installation aId - * @param aExtraFile - * An optional dummy file to create in the directory - * @return An nsIFile for the directory in which the add-on is installed. - */ -function writeInstallRDFToDir(aData, aDir, aId = aData.id, aExtraFile = null) { - let files = { - "install.rdf": AddonTestUtils.createInstallRDF(aData), - }; - if (aExtraFile) - files[aExtraFile] = ""; - - let dir = aDir.clone(); - dir.append(aId); - - awaitPromise(AddonTestUtils.promiseWriteFilesToDir(dir.path, files)); - return dir; -} - -/** - * Writes an install.rdf manifest into a packed extension using the properties passed - * in a JS object. The objects should contain a property for each property to - * appear in the RDF. The object may contain an array of objects with id, - * minVersion and maxVersion in the targetApplications property to give target - * application compatibility. - * - * @param aData - * The object holding data about the add-on - * @param aDir - * The install directory to add the extension to - * @param aId - * An optional string to override the default installation aId - * @param aExtraFile - * An optional dummy file to create in the extension - * @return A file pointing to where the extension was installed - */ -function writeInstallRDFToXPI(aData, aDir, aId = aData.id, aExtraFile = null) { - let files = { - "install.rdf": AddonTestUtils.createInstallRDF(aData), - }; - if (aExtraFile) - files[aExtraFile] = ""; - - if (!aDir.exists()) - aDir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - - var file = aDir.clone(); - file.append(`${aId}.xpi`); - - AddonTestUtils.writeFilesToZip(file.path, files); - - return file; -} - -/** - * Writes an install.rdf manifest into an extension using the properties passed - * in a JS object. The objects should contain a property for each property to - * appear in the RDF. The object may contain an array of objects with id, - * minVersion and maxVersion in the targetApplications property to give target - * application compatibility. - * - * @param aData - * The object holding data about the add-on - * @param aDir - * The install directory to add the extension to - * @param aId - * An optional string to override the default installation aId - * @param aExtraFile - * An optional dummy file to create in the extension - * @return A file pointing to where the extension was installed - */ -function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) { - if (TEST_UNPACKED) { - return writeInstallRDFToDir(aData, aDir, aId, aExtraFile); - } - return writeInstallRDFToXPI(aData, aDir, aId, aExtraFile); -} - -/** - * Writes a manifest.json manifest into an extension using the properties passed - * in a JS object. - * - * @param aManifest - * The data to write - * @param aDir - * The install directory to add the extension to - * @param aId - * An optional string to override the default installation aId - * @return A file pointing to where the extension was installed - */ -function promiseWriteWebManifestForExtension(aData, aDir, aId = aData.applications.gecko.id) { - let files = { - "manifest.json": JSON.stringify(aData), - } - return AddonTestUtils.promiseWriteFilesToExtension(aDir.path, aId, files); -} - -/** - * Creates an XPI file for some manifest data in the temporary directory and - * returns the nsIFile for it. The file will be deleted when the test completes. - * - * @param aData - * The object holding data about the add-on - * @return A file pointing to the created XPI file - */ -function createTempXPIFile(aData, aExtraFile) { - let files = { - "install.rdf": aData, - }; - if (typeof aExtraFile == "object") - Object.assign(files, aExtraFile); - else if (aExtraFile) - files[aExtraFile] = ""; - - return AddonTestUtils.createTempXPIFile(files); -} - -var gExpectedEvents = {}; -var gExpectedInstalls = []; -var gNext = null; - -function getExpectedEvent(aId) { - if (!(aId in gExpectedEvents)) - do_throw("Wasn't expecting events for " + aId); - if (gExpectedEvents[aId].length == 0) - do_throw("Too many events for " + aId); - let event = gExpectedEvents[aId].shift(); - if (event instanceof Array) - return event; - return [event, true]; -} - -function getExpectedInstall(aAddon) { - if (gExpectedInstalls instanceof Array) - return gExpectedInstalls.shift(); - if (!aAddon || !aAddon.id) - return gExpectedInstalls["NO_ID"].shift(); - let id = aAddon.id; - if (!(id in gExpectedInstalls) || !(gExpectedInstalls[id] instanceof Array)) - do_throw("Wasn't expecting events for " + id); - if (gExpectedInstalls[id].length == 0) - do_throw("Too many events for " + id); - return gExpectedInstalls[id].shift(); -} - -const AddonListener = { - onPropertyChanged: function(aAddon, aProperties) { - do_print(`Got onPropertyChanged event for ${aAddon.id}`); - let [event, properties] = getExpectedEvent(aAddon.id); - do_check_eq("onPropertyChanged", event); - do_check_eq(aProperties.length, properties.length); - properties.forEach(function(aProperty) { - // Only test that the expected properties are listed, having additional - // properties listed is not necessary a problem - if (aProperties.indexOf(aProperty) == -1) - do_throw("Did not see property change for " + aProperty); - }); - return check_test_completed(arguments); - }, - - onEnabling: function(aAddon, aRequiresRestart) { - do_print(`Got onEnabling event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onEnabling", event); - do_check_eq(aRequiresRestart, expectedRestart); - if (expectedRestart) - do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_ENABLE)); - do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE)); - return check_test_completed(arguments); - }, - - onEnabled: function(aAddon) { - do_print(`Got onEnabled event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onEnabled", event); - do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_ENABLE)); - return check_test_completed(arguments); - }, - - onDisabling: function(aAddon, aRequiresRestart) { - do_print(`Got onDisabling event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onDisabling", event); - do_check_eq(aRequiresRestart, expectedRestart); - if (expectedRestart) - do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_DISABLE)); - do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE)); - return check_test_completed(arguments); - }, - - onDisabled: function(aAddon) { - do_print(`Got onDisabled event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onDisabled", event); - do_check_false(hasFlag(aAddon.permissions, AddonManager.PERM_CAN_DISABLE)); - return check_test_completed(arguments); - }, - - onInstalling: function(aAddon, aRequiresRestart) { - do_print(`Got onInstalling event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onInstalling", event); - do_check_eq(aRequiresRestart, expectedRestart); - if (expectedRestart) - do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_INSTALL)); - return check_test_completed(arguments); - }, - - onInstalled: function(aAddon) { - do_print(`Got onInstalled event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onInstalled", event); - return check_test_completed(arguments); - }, - - onUninstalling: function(aAddon, aRequiresRestart) { - do_print(`Got onUninstalling event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onUninstalling", event); - do_check_eq(aRequiresRestart, expectedRestart); - if (expectedRestart) - do_check_true(hasFlag(aAddon.pendingOperations, AddonManager.PENDING_UNINSTALL)); - return check_test_completed(arguments); - }, - - onUninstalled: function(aAddon) { - do_print(`Got onUninstalled event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onUninstalled", event); - return check_test_completed(arguments); - }, - - onOperationCancelled: function(aAddon) { - do_print(`Got onOperationCancelled event for ${aAddon.id}`); - let [event, expectedRestart] = getExpectedEvent(aAddon.id); - do_check_eq("onOperationCancelled", event); - return check_test_completed(arguments); - } -}; - -const InstallListener = { - onNewInstall: function(install) { - if (install.state != AddonManager.STATE_DOWNLOADED && - install.state != AddonManager.STATE_DOWNLOAD_FAILED && - install.state != AddonManager.STATE_AVAILABLE) - do_throw("Bad install state " + install.state); - if (install.state != AddonManager.STATE_DOWNLOAD_FAILED) - do_check_eq(install.error, 0); - else - do_check_neq(install.error, 0); - do_check_eq("onNewInstall", getExpectedInstall()); - return check_test_completed(arguments); - }, - - onDownloadStarted: function(install) { - do_check_eq(install.state, AddonManager.STATE_DOWNLOADING); - do_check_eq(install.error, 0); - do_check_eq("onDownloadStarted", getExpectedInstall()); - return check_test_completed(arguments); - }, - - onDownloadEnded: function(install) { - do_check_eq(install.state, AddonManager.STATE_DOWNLOADED); - do_check_eq(install.error, 0); - do_check_eq("onDownloadEnded", getExpectedInstall()); - return check_test_completed(arguments); - }, - - onDownloadFailed: function(install) { - do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED); - do_check_eq("onDownloadFailed", getExpectedInstall()); - return check_test_completed(arguments); - }, - - onDownloadCancelled: function(install) { - do_check_eq(install.state, AddonManager.STATE_CANCELLED); - do_check_eq(install.error, 0); - do_check_eq("onDownloadCancelled", getExpectedInstall()); - return check_test_completed(arguments); - }, - - onInstallStarted: function(install) { - do_check_eq(install.state, AddonManager.STATE_INSTALLING); - do_check_eq(install.error, 0); - do_check_eq("onInstallStarted", getExpectedInstall(install.addon)); - return check_test_completed(arguments); - }, - - onInstallEnded: function(install, newAddon) { - do_check_eq(install.state, AddonManager.STATE_INSTALLED); - do_check_eq(install.error, 0); - do_check_eq("onInstallEnded", getExpectedInstall(install.addon)); - return check_test_completed(arguments); - }, - - onInstallFailed: function(install) { - do_check_eq(install.state, AddonManager.STATE_INSTALL_FAILED); - do_check_eq("onInstallFailed", getExpectedInstall(install.addon)); - return check_test_completed(arguments); - }, - - onInstallCancelled: function(install) { - // If the install was cancelled by a listener returning false from - // onInstallStarted, then the state will revert to STATE_DOWNLOADED. - let possibleStates = [AddonManager.STATE_CANCELLED, - AddonManager.STATE_DOWNLOADED]; - do_check_true(possibleStates.indexOf(install.state) != -1); - do_check_eq(install.error, 0); - do_check_eq("onInstallCancelled", getExpectedInstall(install.addon)); - return check_test_completed(arguments); - }, - - onExternalInstall: function(aAddon, existingAddon, aRequiresRestart) { - do_check_eq("onExternalInstall", getExpectedInstall(aAddon)); - do_check_false(aRequiresRestart); - return check_test_completed(arguments); - } -}; - -function hasFlag(aBits, aFlag) { - return (aBits & aFlag) != 0; -} - -// Just a wrapper around setting the expected events -function prepare_test(aExpectedEvents, aExpectedInstalls, aNext) { - AddonManager.addAddonListener(AddonListener); - AddonManager.addInstallListener(InstallListener); - - gExpectedInstalls = aExpectedInstalls; - gExpectedEvents = aExpectedEvents; - gNext = aNext; -} - -// Checks if all expected events have been seen and if so calls the callback -function check_test_completed(aArgs) { - if (!gNext) - return undefined; - - if (gExpectedInstalls instanceof Array && - gExpectedInstalls.length > 0) - return undefined; - - for (let id in gExpectedInstalls) { - let installList = gExpectedInstalls[id]; - if (installList.length > 0) - return undefined; - } - - for (let id in gExpectedEvents) { - if (gExpectedEvents[id].length > 0) - return undefined; - } - - return gNext.apply(null, aArgs); -} - -// Verifies that all the expected events for all add-ons were seen -function ensure_test_completed() { - for (let i in gExpectedEvents) { - if (gExpectedEvents[i].length > 0) - do_throw("Didn't see all the expected events for " + i); - } - gExpectedEvents = {}; - if (gExpectedInstalls) - do_check_eq(gExpectedInstalls.length, 0); -} - -/** - * A helper method to install an array of AddonInstall to completion and then - * call a provided callback. - * - * @param aInstalls - * The array of AddonInstalls to install - * @param aCallback - * The callback to call when all installs have finished - */ -function completeAllInstalls(aInstalls, aCallback) { - promiseCompleteAllInstalls(aInstalls).then(aCallback); -} - -/** - * A helper method to install an array of files and call a callback after the - * installs are completed. - * - * @param aFiles - * The array of files to install - * @param aCallback - * The callback to call when all installs have finished - * @param aIgnoreIncompatible - * Optional parameter to ignore add-ons that are incompatible in - * aome way with the application - */ -function installAllFiles(aFiles, aCallback, aIgnoreIncompatible) { - promiseInstallAllFiles(aFiles, aIgnoreIncompatible).then(aCallback); -} - -const EXTENSIONS_DB = "extensions.json"; -var gExtensionsJSON = gProfD.clone(); -gExtensionsJSON.append(EXTENSIONS_DB); - - -// By default use strict compatibility -Services.prefs.setBoolPref("extensions.strictCompatibility", true); - -// By default, set min compatible versions to 0 -Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0"); -Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0"); - -// Ensure signature checks are enabled by default -Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true); - - -// Copies blocklistFile (an nsIFile) to gProfD/blocklist.xml. -function copyBlocklistToProfile(blocklistFile) { - var dest = gProfD.clone(); - dest.append("blocklist.xml"); - if (dest.exists()) - dest.remove(false); - blocklistFile.copyTo(gProfD, "blocklist.xml"); - dest.lastModifiedTime = Date.now(); -} - -// Throw a failure and attempt to abandon the test if it looks like it is going -// to timeout -function timeout() { - timer = null; - do_throw("Test ran longer than " + TIMEOUT_MS + "ms"); - - // Attempt to bail out of the test - do_test_finished(); -} - -var timer = AM_Cc["@mozilla.org/timer;1"].createInstance(AM_Ci.nsITimer); -timer.init(timeout, TIMEOUT_MS, AM_Ci.nsITimer.TYPE_ONE_SHOT); - -// Make sure that a given path does not exist -function pathShouldntExist(file) { - if (file.exists()) { - do_throw(`Test cleanup: path ${file.path} exists when it should not`); - } -} - -do_register_cleanup(function addon_cleanup() { - if (timer) - timer.cancel(); -}); - -/** - * Creates a new HttpServer for testing, and begins listening on the - * specified port. Automatically shuts down the server when the test - * unit ends. - * - * @param port - * The port to listen on. If omitted, listen on a random - * port. The latter is the preferred behavior. - * - * @return HttpServer - */ -function createHttpServer(port = -1) { - let server = new HttpServer(); - server.start(port); - - do_register_cleanup(() => { - return new Promise(resolve => { - server.stop(resolve); - }); - }); - - return server; -} - -/** - * Handler function that responds with the interpolated - * static file associated to the URL specified by request.path. - * This replaces the %PORT% entries in the file with the actual - * value of the running server's port (stored in gPort). - */ -function interpolateAndServeFile(request, response) { - try { - let file = gUrlToFileMap[request.path]; - var data = ""; - var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fstream.init(file, -1, 0, 0); - cstream.init(fstream, "UTF-8", 0, 0); - - let str = {}; - let read = 0; - do { - // read as much as we can and put it in str.value - read = cstream.readString(0xffffffff, str); - data += str.value; - } while (read != 0); - data = data.replace(/%PORT%/g, gPort); - - response.write(data); - } catch (e) { - do_throw(`Exception while serving interpolated file: ${e}\n${e.stack}`); - } finally { - cstream.close(); // this closes fstream as well - } -} - -/** - * Sets up a path handler for the given URL and saves the - * corresponding file in the global url -> file map. - * - * @param url - * the actual URL - * @param file - * nsILocalFile representing a static file - */ -function mapUrlToFile(url, file, server) { - server.registerPathHandler(url, interpolateAndServeFile); - gUrlToFileMap[url] = file; -} - -function mapFile(path, server) { - mapUrlToFile(path, do_get_file(path), server); -} - -/** - * Take out the port number in an URL - * - * @param url - * String that represents an URL with a port number in it - */ -function remove_port(url) { - if (typeof url === "string") - return url.replace(/:\d+/, ""); - return url; -} -// Wrap a function (typically a callback) to catch and report exceptions -function do_exception_wrap(func) { - return function() { - try { - func.apply(null, arguments); - } - catch (e) { - do_report_unexpected_exception(e); - } - }; -} - -/** - * Change the schema version of the JSON extensions database - */ -function changeXPIDBVersion(aNewVersion, aMutator = undefined) { - let jData = loadJSON(gExtensionsJSON); - jData.schemaVersion = aNewVersion; - if (aMutator) - aMutator(jData); - saveJSON(jData, gExtensionsJSON); -} - -/** - * Load a file into a string - */ -function loadFile(aFile) { - let data = ""; - let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. - createInstance(Components.interfaces.nsIFileInputStream); - let cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. - createInstance(Components.interfaces.nsIConverterInputStream); - fstream.init(aFile, -1, 0, 0); - cstream.init(fstream, "UTF-8", 0, 0); - let str = {}; - let read = 0; - do { - read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value - data += str.value; - } while (read != 0); - cstream.close(); - return data; -} - -/** - * Raw load of a JSON file - */ -function loadJSON(aFile) { - let data = loadFile(aFile); - do_print("Loaded JSON file " + aFile.path); - return (JSON.parse(data)); -} - -/** - * Raw save of a JSON blob to file - */ -function saveJSON(aData, aFile) { - do_print("Starting to save JSON file " + aFile.path); - let stream = FileUtils.openSafeFileOutputStream(aFile); - let converter = AM_Cc["@mozilla.org/intl/converter-output-stream;1"]. - createInstance(AM_Ci.nsIConverterOutputStream); - converter.init(stream, "UTF-8", 0, 0x0000); - // XXX pretty print the JSON while debugging - converter.writeString(JSON.stringify(aData, null, 2)); - converter.flush(); - // nsConverterOutputStream doesn't finish() safe output streams on close() - FileUtils.closeSafeFileOutputStream(stream); - converter.close(); - do_print("Done saving JSON file " + aFile.path); -} - -/** - * Create a callback function that calls do_execute_soon on an actual callback and arguments - */ -function callback_soon(aFunction) { - return function(...args) { - do_execute_soon(function() { - aFunction.apply(null, args); - }, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback"); - } -} - -function writeProxyFileToDir(aDir, aAddon, aId) { - awaitPromise(promiseWriteProxyFileToDir(aDir, aAddon, aId)); - - let file = aDir.clone(); - file.append(aId); - return file -} - -function* serveSystemUpdate(xml, perform_update, testserver) { - testserver.registerPathHandler("/data/update.xml", (request, response) => { - response.write(xml); - }); - - try { - yield 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. -function* installSystemAddons(xml, testserver) { - do_print("Triggering system add-on update check."); - - yield serveSystemUpdate(xml, function*() { - let { XPIProvider } = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm", {}); - yield XPIProvider.updateSystemAddons(); - }, testserver); -} - -// Runs a full add-on update check which will in some cases do a system add-on -// update check. Always succeeds. -function* updateAllSystemAddons(xml, testserver) { - do_print("Triggering full add-on update check."); - - yield serveSystemUpdate(xml, function() { - return new Promise(resolve => { - Services.obs.addObserver(function() { - Services.obs.removeObserver(arguments.callee, "addons-background-update-complete"); - - resolve(); - }, "addons-background-update-complete", false); - - // 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, root) { - let xml = `<?xml version="1.0" encoding="UTF-8"?>\n\n<updates>\n`; - if (addons) { - xml += ` <addons>\n`; - for (let addon of addons) { - xml += ` <addon id="${addon.id}" URL="${root + addon.path}" version="${addon.version}"`; - if (addon.size) - xml += ` size="${addon.size}"`; - if (addon.hashFunction) - xml += ` hashFunction="${addon.hashFunction}"`; - if (addon.hashValue) - xml += ` hashValue="${addon.hashValue}"`; - xml += `/>\n`; - } - xml += ` </addons>\n`; - } - xml += `</updates>\n`; - - return xml; -} |