diff options
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/head_addons.js')
-rw-r--r-- | toolkit/mozapps/extensions/test/xpcshell/head_addons.js | 1759 |
1 files changed, 0 insertions, 1759 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js deleted file mode 100644 index 60259944e..000000000 --- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js +++ /dev/null @@ -1,1759 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -const AM_Cc = Components.classes; -const AM_Ci = Components.interfaces; - -const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1"; -const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}"); - -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"; - -// Forcibly end the test if it runs longer than 15 minutes -const TIMEOUT_MS = 900000; - -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"); -Components.utils.import("resource://gre/modules/osfile.jsm"); -Components.utils.import("resource://gre/modules/AsyncShutdown.jsm"); - -Services.prefs.setBoolPref("toolkit.osfile.log", true); - -// We need some internal bits of AddonManager -let AMscope = Components.utils.import("resource://gre/modules/AddonManager.jsm"); -let AddonManager = AMscope.AddonManager; -let AddonManagerInternal = AMscope.AddonManagerInternal; -// Mock out AddonManager's reference to the AsyncShutdown module so we can shut -// down AddonManager from the test -let MockAsyncShutdown = { - hook: null, - status: null, - profileBeforeChange: { - addBlocker: function(aName, aBlocker, aOptions) { - do_print("Mock profileBeforeChange blocker for '" + aName + "'"); - MockAsyncShutdown.hook = aBlocker; - MockAsyncShutdown.status = aOptions.fetchState; - } - }, - // We can use the real Barrier - Barrier: AsyncShutdown.Barrier -}; - -AMscope.AsyncShutdown = MockAsyncShutdown; - -var gInternalManager = null; -var gAppInfo = null; -var gAddonsList; - -var gPort = null; -var gUrlToFileMap = {}; - -var TEST_UNPACKED = false; - -function isNightlyChannel() { - var channel = "default"; - try { - channel = Services.prefs.getCharPref("app.update.channel"); - } - catch (e) { } - - return channel != "aurora" && channel != "beta" && channel != "release" && channel != "esr"; -} - -function createAppInfo(id, name, version, platformVersion) { - gAppInfo = { - // nsIXULAppInfo - vendor: "Mozilla", - name: name, - ID: id, - version: version, - appBuildID: "2007010101", - platformVersion: platformVersion ? platformVersion : "1.0", - platformBuildID: "2007010101", - - // nsIXULRuntime - inSafeMode: false, - logConsoleErrors: true, - OS: "XPCShell", - XPCOMABI: "noarch-spidermonkey", - invalidateCachesOnRestart: function invalidateCachesOnRestart() { - // Do nothing - }, - - // nsICrashReporter - annotations: {}, - - annotateCrashReport: function(key, data) { - this.annotations[key] = data; - }, - - QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIXULAppInfo, - AM_Ci.nsIXULRuntime, - AM_Ci.nsICrashReporter, - AM_Ci.nsISupports]) - }; - - var XULAppInfoFactory = { - createInstance: function (outer, iid) { - if (outer != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - return gAppInfo.QueryInterface(iid); - } - }; - var registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar); - registrar.registerFactory(XULAPPINFO_CID, "XULAppInfo", - XULAPPINFO_CONTRACTID, XULAppInfoFactory); -} - -/** - * 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 - function toHexString(charCode) - ("0" + charCode.toString(16)).slice(-2); - - let binary = crypto.finish(false); - return aAlgorithm + ":" + [toHexString(binary.charCodeAt(i)) for (i in binary)].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 + "!/"; - } - else { - 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])); - } -} - -// Record the error (if any) from trying to save the XPI -// database at shutdown time -let gXPISaveError = null; - -/** - * Starts up the add-on manager as if it was started by the application. - * - * @param aAppChanged - * An optional boolean parameter to simulate the case where the - * application has changed version since the last run. If not passed it - * defaults to true - */ -function startupManager(aAppChanged) { - if (gInternalManager) - do_throw("Test attempt to startup manager that was already started."); - - if (aAppChanged || aAppChanged === undefined) { - if (gExtensionsINI.exists()) - gExtensionsINI.remove(true); - } - - gInternalManager = AM_Cc["@mozilla.org/addons/integration;1"]. - getService(AM_Ci.nsIObserver). - QueryInterface(AM_Ci.nsITimerCallback); - - gInternalManager.observe(null, "addons-startup", null); - - // Load the add-ons list as it was after extension registration - loadAddonsList(); -} - -/** - * Helper to spin the event loop until a promise resolves or rejects - */ -function loopUntilPromise(aPromise) { - let done = false; - aPromise.then( - () => done = true, - err => { - do_report_unexpected_exception(err); - done = true; - }); - - let thr = Services.tm.mainThread; - - while (!done) { - thr.processNextEvent(true); - } -} - -/** - * 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) { - loopUntilPromise(promiseRestartManager(aNewVersion)); -} - -function promiseRestartManager(aNewVersion) { - return promiseShutdownManager() - .then(null, err => do_report_unexpected_exception(err)) - .then(() => { - if (aNewVersion) { - gAppInfo.version = aNewVersion; - startupManager(true); - } - else { - startupManager(false); - } - }); -} - -function shutdownManager() { - loopUntilPromise(promiseShutdownManager()); -} - -function promiseShutdownManager() { - if (!gInternalManager) { - return Promise.resolve(false); - } - - let hookErr = null; - Services.obs.notifyObservers(null, "quit-application-granted", null); - return MockAsyncShutdown.hook() - .then(null, err => hookErr = err) - .then( () => { - gInternalManager = null; - - // Load the add-ons list as it was after application shutdown - loadAddonsList(); - - // Clear any crash report annotations - gAppInfo.annotations = {}; - - // Force the XPIProvider provider to reload to better - // simulate real-world usage. - let XPIscope = Components.utils.import("resource://gre/modules/addons/XPIProvider.jsm"); - // This would be cleaner if I could get it as the rejection reason from - // the AddonManagerInternal.shutdown() promise - gXPISaveError = XPIscope.XPIProvider._shutdownError; - do_print("gXPISaveError set to: " + gXPISaveError); - AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider); - Components.utils.unload("resource://gre/modules/addons/XPIProvider.jsm"); - if (hookErr) { - throw hookErr; - } - }); -} - -function loadAddonsList() { - function readDirectories(aSection) { - var dirs = []; - var keys = parser.getKeys(aSection); - while (keys.hasMore()) { - let descriptor = parser.getString(aSection, keys.getNext()); - try { - let file = AM_Cc["@mozilla.org/file/local;1"]. - createInstance(AM_Ci.nsIFile); - file.persistentDescriptor = descriptor; - dirs.push(file); - } - catch (e) { - // Throws if the directory doesn't exist, we can ignore this since the - // platform will too. - } - } - return dirs; - } - - gAddonsList = { - extensions: [], - themes: [], - mpIncompatible: new Set() - }; - - if (!gExtensionsINI.exists()) - return; - - var factory = AM_Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. - getService(AM_Ci.nsIINIParserFactory); - var parser = factory.createINIParser(gExtensionsINI); - gAddonsList.extensions = readDirectories("ExtensionDirs"); - gAddonsList.themes = readDirectories("ThemeDirs"); - var keys = parser.getKeys("MultiprocessIncompatibleExtensions"); - while (keys.hasMore()) { - let id = parser.getString("MultiprocessIncompatibleExtensions", keys.getNext()); - gAddonsList.mpIncompatible.add(id); - } -} - -function isItemInAddonsList(aType, aDir, aId) { - var path = aDir.clone(); - path.append(aId); - var xpiPath = aDir.clone(); - xpiPath.append(aId + ".xpi"); - for (var i = 0; i < gAddonsList[aType].length; i++) { - let file = gAddonsList[aType][i]; - if (!file.exists()) - do_throw("Non-existant path found in extensions.ini: " + file.path) - if (file.isDirectory() && file.equals(path)) - return true; - if (file.isFile() && file.equals(xpiPath)) - return true; - } - return false; -} - -function isItemMarkedMPIncompatible(aId) { - return gAddonsList.mpIncompatible.has(aId); -} - -function isThemeInAddonsList(aDir, aId) { - return isItemInAddonsList("themes", aDir, aId); -} - -function isExtensionInAddonsList(aDir, aId) { - return isItemInAddonsList("extensions", aDir, aId); -} - -function check_startup_changes(aType, aIds) { - var ids = aIds.slice(0); - ids.sort(); - var changes = AddonManager.getStartupChanges(aType); - changes = changes.filter(function(aEl) /@tests.mozilla.org$/.test(aEl)); - changes.sort(); - - do_check_eq(JSON.stringify(ids), JSON.stringify(changes)); -} - -/** - * Escapes any occurances of &, ", < or > with XML entities. - * - * @param str - * The string to escape - * @return The escaped string - */ -function escapeXML(aStr) { - return aStr.toString() - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(/</g, "<") - .replace(/>/g, ">"); -} - -function writeLocaleStrings(aData) { - let rdf = ""; - ["name", "description", "creator", "homepageURL"].forEach(function(aProp) { - if (aProp in aData) - rdf += "<em:" + aProp + ">" + escapeXML(aData[aProp]) + "</em:" + aProp + ">\n"; - }); - - ["developer", "translator", "contributor"].forEach(function(aProp) { - if (aProp in aData) { - aData[aProp].forEach(function(aValue) { - rdf += "<em:" + aProp + ">" + escapeXML(aValue) + "</em:" + aProp + ">\n"; - }); - } - }); - return rdf; -} - -/** - * Creates an update.rdf structure as a string using for the update data passed. - * - * @param aData - * The update data as a JS object. Each property name is an add-on ID, - * the property value is an array of each version of the add-on. Each - * array value is a JS object containing the data for the version, at - * minimum a "version" and "targetApplications" property should be - * included to create a functional update manifest. - * @return the update.rdf structure as a string. - */ -function createUpdateRDF(aData) { - var rdf = '<?xml version="1.0"?>\n'; - rdf += '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' + - ' xmlns:em="http://www.mozilla.org/2004/em-rdf#">\n'; - - for (let addon in aData) { - rdf += ' <Description about="urn:mozilla:extension:' + escapeXML(addon) + '"><em:updates><Seq>\n'; - - for (let versionData of aData[addon]) { - rdf += ' <li><Description>\n'; - - for (let prop of ["version", "multiprocessCompatible"]) { - if (prop in versionData) - rdf += " <em:" + prop + ">" + escapeXML(versionData[prop]) + "</em:" + prop + ">\n"; - } - - if ("targetApplications" in versionData) { - for (let app of versionData.targetApplications) { - rdf += " <em:targetApplication><Description>\n"; - for (let prop of ["id", "minVersion", "maxVersion", "updateLink", "updateHash"]) { - if (prop in app) - rdf += " <em:" + prop + ">" + escapeXML(app[prop]) + "</em:" + prop + ">\n"; - } - rdf += " </Description></em:targetApplication>\n"; - } - } - - rdf += ' </Description></li>\n'; - } - - rdf += ' </Seq></em:updates></Description>\n' - } - rdf += "</RDF>\n"; - - return rdf; -} - -function createInstallRDF(aData) { - var rdf = '<?xml version="1.0"?>\n'; - rdf += '<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' + - ' xmlns:em="http://www.mozilla.org/2004/em-rdf#">\n'; - rdf += '<Description about="urn:mozilla:install-manifest">\n'; - - ["id", "version", "type", "internalName", "updateURL", "updateKey", - "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL", - "skinnable", "bootstrap", "strictCompatibility", "multiprocessCompatible"].forEach(function(aProp) { - if (aProp in aData) - rdf += "<em:" + aProp + ">" + escapeXML(aData[aProp]) + "</em:" + aProp + ">\n"; - }); - - rdf += writeLocaleStrings(aData); - - if ("targetPlatforms" in aData) { - aData.targetPlatforms.forEach(function(aPlatform) { - rdf += "<em:targetPlatform>" + escapeXML(aPlatform) + "</em:targetPlatform>\n"; - }); - } - - if ("targetApplications" in aData) { - aData.targetApplications.forEach(function(aApp) { - rdf += "<em:targetApplication><Description>\n"; - ["id", "minVersion", "maxVersion"].forEach(function(aProp) { - if (aProp in aApp) - rdf += "<em:" + aProp + ">" + escapeXML(aApp[aProp]) + "</em:" + aProp + ">\n"; - }); - rdf += "</Description></em:targetApplication>\n"; - }); - } - - if ("localized" in aData) { - aData.localized.forEach(function(aLocalized) { - rdf += "<em:localized><Description>\n"; - if ("locale" in aLocalized) { - aLocalized.locale.forEach(function(aLocaleName) { - rdf += "<em:locale>" + escapeXML(aLocaleName) + "</em:locale>\n"; - }); - } - rdf += writeLocaleStrings(aLocalized); - rdf += "</Description></em:localized>\n"; - }); - } - - rdf += "</Description>\n</RDF>\n"; - return rdf; -} - -/** - * 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, aExtraFile) { - var id = aId ? aId : aData.id - - var dir = aDir.clone(); - dir.append(id); - - var rdf = createInstallRDF(aData); - if (!dir.exists()) - dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - var file = dir.clone(); - file.append("install.rdf"); - if (file.exists()) - file.remove(true); - var fos = AM_Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(AM_Ci.nsIFileOutputStream); - fos.init(file, - FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE, - FileUtils.PERMS_FILE, 0); - fos.write(rdf, rdf.length); - fos.close(); - - if (!aExtraFile) - return dir; - - file = dir.clone(); - file.append(aExtraFile); - file.create(AM_Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); - return dir; -} - -/** - * 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 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, aExtraFile) { - var id = aId ? aId : aData.id - - if (!aDir.exists()) - aDir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - - var file = aDir.clone(); - file.append(id + ".xpi"); - writeInstallRDFToXPIFile(aData, file, aExtraFile); - - return file; -} - -/** - * Writes an install.rdf manifest into an XPI file 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 aFile - * The XPI file to write to. Any existing file will be overwritten - * @param aExtraFile - * An optional dummy file to create in the extension - */ -function writeInstallRDFToXPIFile(aData, aFile, aExtraFile) { - var rdf = createInstallRDF(aData); - var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(AM_Ci.nsIStringInputStream); - stream.setData(rdf, -1); - var zipW = AM_Cc["@mozilla.org/zipwriter;1"]. - createInstance(AM_Ci.nsIZipWriter); - zipW.open(aFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE); - zipW.addEntryStream("install.rdf", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE, - stream, false); - if (aExtraFile) - zipW.addEntryStream(aExtraFile, 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE, - stream, false); - zipW.close(); -} - -let temp_xpis = []; -/** - * 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) { - var file = gTmpD.clone(); - file.append("foo.xpi"); - do { - file.leafName = Math.floor(Math.random() * 1000000) + ".xpi"; - } while (file.exists()); - - temp_xpis.push(file); - writeInstallRDFToXPIFile(aData, file); - return file; -} - -/** - * Sets the last modified time of the extension, usually to trigger an update - * of its metadata. If the extension is unpacked, this function assumes that - * the extension contains only the install.rdf file. - * - * @param aExt a file pointing to either the packed extension or its unpacked directory. - * @param aTime the time to which we set the lastModifiedTime of the extension - * - * @deprecated Please use promiseSetExtensionModifiedTime instead - */ -function setExtensionModifiedTime(aExt, aTime) { - aExt.lastModifiedTime = aTime; - if (aExt.isDirectory()) { - let entries = aExt.directoryEntries - .QueryInterface(AM_Ci.nsIDirectoryEnumerator); - while (entries.hasMoreElements()) - setExtensionModifiedTime(entries.nextFile, aTime); - entries.close(); - } -} -function promiseSetExtensionModifiedTime(aPath, aTime) { - return Task.spawn(function* () { - yield OS.File.setDates(aPath, aTime, aTime); - let entries, iterator; - try { - let iterator = new OS.File.DirectoryIterator(aPath); - entries = yield iterator.nextBatch(); - } catch (ex if ex instanceof OS.File.Error) { - return; - } finally { - if (iterator) { - iterator.close(); - } - } - for (let entry of entries) { - yield promiseSetExtensionModifiedTime(entry.path, aTime); - } - }); -} - -/** - * Manually installs an XPI file into an install location by either copying the - * XPI there or extracting it depending on whether unpacking is being tested - * or not. - * - * @param aXPIFile - * The XPI file to install. - * @param aInstallLocation - * The install location (an nsIFile) to install into. - * @param aID - * The ID to install as. - */ -function manuallyInstall(aXPIFile, aInstallLocation, aID) { - if (TEST_UNPACKED) { - let dir = aInstallLocation.clone(); - dir.append(aID); - dir.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); - let zip = AM_Cc["@mozilla.org/libjar/zip-reader;1"]. - createInstance(AM_Ci.nsIZipReader); - zip.open(aXPIFile); - let entries = zip.findEntries(null); - while (entries.hasMore()) { - let entry = entries.getNext(); - let target = dir.clone(); - entry.split("/").forEach(function(aPart) { - target.append(aPart); - }); - zip.extract(entry, target); - } - zip.close(); - - return dir; - } - else { - let target = aInstallLocation.clone(); - target.append(aID + ".xpi"); - aXPIFile.copyTo(target.parent, target.leafName); - return target; - } -} - -/** - * Manually uninstalls an add-on by removing its files from the install - * location. - * - * @param aInstallLocation - * The nsIFile of the install location to remove from. - * @param aID - * The ID of the add-on to remove. - */ -function manuallyUninstall(aInstallLocation, aID) { - let file = getFileForAddon(aInstallLocation, aID); - - // In reality because the app is restarted a flush isn't necessary for XPIs - // removed outside the app, but for testing we must flush manually. - if (file.isFile()) - Services.obs.notifyObservers(file, "flush-cache-entry", null); - - file.remove(true); -} - -/** - * Gets the nsIFile for where an add-on is installed. It may point to a file or - * a directory depending on whether add-ons are being installed unpacked or not. - * - * @param aDir - * The nsIFile for the install location - * @param aId - * The ID of the add-on - * @return an nsIFile - */ -function getFileForAddon(aDir, aId) { - var dir = aDir.clone(); - dir.append(do_get_expected_addon_name(aId)); - return dir; -} - -function registerDirectory(aKey, aDir) { - var dirProvider = { - getFile: function(aProp, aPersistent) { - aPersistent.value = true; - if (aProp == aKey) - return aDir.clone(); - return null; - }, - - QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIDirectoryServiceProvider, - AM_Ci.nsISupports]) - }; - Services.dirsvc.registerProvider(dirProvider); -} - -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_AVAILABLE) - do_throw("Bad install state " + install.state); - do_check_eq(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; - else for each (let installList in gExpectedInstalls) { - 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) { - let count = aInstalls.length; - - if (count == 0) { - aCallback(); - return; - } - - function installCompleted(aInstall) { - aInstall.removeListener(listener); - - if (--count == 0) - do_execute_soon(aCallback); - } - - let listener = { - onDownloadFailed: installCompleted, - onDownloadCancelled: installCompleted, - onInstallFailed: installCompleted, - onInstallCancelled: installCompleted, - onInstallEnded: installCompleted - }; - - aInstalls.forEach(function(aInstall) { - aInstall.addListener(listener); - aInstall.install(); - }); -} - -function promiseCompleteAllInstalls(aInstalls) { - return new Promise(resolve => { - completeAllInstalls(aInstalls, resolve); - }); -} - -/** - * 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) { - let count = aFiles.length; - let installs = []; - function callback() { - if (aCallback) { - aCallback(); - } - } - aFiles.forEach(function(aFile) { - AddonManager.getInstallForFile(aFile, function(aInstall) { - if (!aInstall) - do_throw("No AddonInstall created for " + aFile.path); - do_check_eq(aInstall.state, AddonManager.STATE_DOWNLOADED); - - if (!aIgnoreIncompatible || !aInstall.addon.appDisabled) - installs.push(aInstall); - - if (--count == 0) - completeAllInstalls(installs, callback); - }); - }); -} - -function promiseInstallAllFiles(aFiles, aIgnoreIncompatible) { - let deferred = Promise.defer(); - installAllFiles(aFiles, deferred.resolve, aIgnoreIncompatible); - return deferred.promise; - -} - -if ("nsIWindowsRegKey" in AM_Ci) { - var MockRegistry = { - LOCAL_MACHINE: {}, - CURRENT_USER: {}, - CLASSES_ROOT: {}, - - getRoot: function(aRoot) { - switch (aRoot) { - case AM_Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE: - return MockRegistry.LOCAL_MACHINE; - case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER: - return MockRegistry.CURRENT_USER; - case AM_Ci.nsIWindowsRegKey.ROOT_KEY_CLASSES_ROOT: - return MockRegistry.CLASSES_ROOT; - default: - do_throw("Unknown root " + aRootKey); - return null; - } - }, - - setValue: function(aRoot, aPath, aName, aValue) { - let rootKey = MockRegistry.getRoot(aRoot); - - if (!(aPath in rootKey)) { - rootKey[aPath] = []; - } - else { - for (let i = 0; i < rootKey[aPath].length; i++) { - if (rootKey[aPath][i].name == aName) { - if (aValue === null) - rootKey[aPath].splice(i, 1); - else - rootKey[aPath][i].value = aValue; - return; - } - } - } - - if (aValue === null) - return; - - rootKey[aPath].push({ - name: aName, - value: aValue - }); - } - }; - - /** - * This is a mock nsIWindowsRegistry implementation. It only implements the - * methods that the extension manager requires. - */ - function MockWindowsRegKey() { - } - - MockWindowsRegKey.prototype = { - values: null, - - // --- Overridden nsISupports interface functions --- - QueryInterface: XPCOMUtils.generateQI([AM_Ci.nsIWindowsRegKey]), - - // --- Overridden nsIWindowsRegKey interface functions --- - open: function(aRootKey, aRelPath, aMode) { - let rootKey = MockRegistry.getRoot(aRootKey); - - if (!(aRelPath in rootKey)) - rootKey[aRelPath] = []; - this.values = rootKey[aRelPath]; - }, - - close: function() { - this.values = null; - }, - - get valueCount() { - if (!this.values) - throw Components.results.NS_ERROR_FAILURE; - return this.values.length; - }, - - getValueName: function(aIndex) { - if (!this.values || aIndex >= this.values.length) - throw Components.results.NS_ERROR_FAILURE; - return this.values[aIndex].name; - }, - - readStringValue: function(aName) { - for (let value of this.values) { - if (value.name == aName) - return value.value; - } - return null; - } - }; - - var WinRegFactory = { - createInstance: function(aOuter, aIid) { - if (aOuter != null) - throw Components.results.NS_ERROR_NO_AGGREGATION; - - var key = new MockWindowsRegKey(); - return key.QueryInterface(aIid); - } - }; - - var registrar = Components.manager.QueryInterface(AM_Ci.nsIComponentRegistrar); - registrar.registerFactory(Components.ID("{0478de5b-0f38-4edb-851d-4c99f1ed8eba}"), - "Mock Windows Registry Implementation", - "@mozilla.org/windows-registry-key;1", WinRegFactory); -} - -// Get the profile directory for tests to use. -const gProfD = do_get_profile(); - -const EXTENSIONS_DB = "extensions.json"; -let gExtensionsJSON = gProfD.clone(); -gExtensionsJSON.append(EXTENSIONS_DB); - -const EXTENSIONS_INI = "extensions.ini"; -let gExtensionsINI = gProfD.clone(); -gExtensionsINI.append(EXTENSIONS_INI); - -// Enable more extensive EM logging -Services.prefs.setBoolPref("extensions.logging.enabled", true); - -// By default only load extensions from the profile install location -Services.prefs.setIntPref("extensions.enabledScopes", AddonManager.SCOPE_PROFILE); - -// By default don't disable add-ons from any scope -Services.prefs.setIntPref("extensions.autoDisableScopes", 0); - -// By default, don't cache add-ons in AddonRepository.jsm -Services.prefs.setBoolPref("extensions.getAddons.cache.enabled", false); - -// Disable the compatibility updates window by default -Services.prefs.setBoolPref("extensions.showMismatchUI", false); - -// Point update checks to the local machine for fast failures -Services.prefs.setCharPref("extensions.update.url", "http://127.0.0.1/updateURL"); -Services.prefs.setCharPref("extensions.update.background.url", "http://127.0.0.1/updateBackgroundURL"); -Services.prefs.setCharPref("extensions.blocklist.url", "http://127.0.0.1/blocklistURL"); - -// By default ignore bundled add-ons -Services.prefs.setBoolPref("extensions.installDistroAddons", false); - -// 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"); - -// Register a temporary directory for the tests. -const gTmpD = gProfD.clone(); -gTmpD.append("temp"); -gTmpD.create(AM_Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); -registerDirectory("TmpD", gTmpD); - -// Write out an empty blocklist.xml file to the profile to ensure nothing -// is blocklisted by default -var blockFile = gProfD.clone(); -blockFile.append("blocklist.xml"); -var stream = AM_Cc["@mozilla.org/network/file-output-stream;1"]. - createInstance(AM_Ci.nsIFileOutputStream); -stream.init(blockFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE, - FileUtils.PERMS_FILE, 0); - -var data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + - "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">\n" + - "</blocklist>\n"; -stream.write(data, data.length); -stream.close(); - -// 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(aPath) { - if (aPath.exists()) { - do_throw("Test cleanup: path " + aPath.path + " exists when it should not"); - } -} - -do_register_cleanup(function addon_cleanup() { - if (timer) - timer.cancel(); - - for (let file of temp_xpis) { - if (file.exists()) - file.remove(false); - } - - // Check that the temporary directory is empty - var dirEntries = gTmpD.directoryEntries - .QueryInterface(AM_Ci.nsIDirectoryEnumerator); - var entry; - while ((entry = dirEntries.nextFile)) { - do_throw("Found unexpected file in temporary directory: " + entry.leafName); - } - dirEntries.close(); - - var testDir = gProfD.clone(); - testDir.append("extensions"); - testDir.append("trash"); - pathShouldntExist(testDir); - - testDir.leafName = "staged"; - pathShouldntExist(testDir); - - testDir.leafName = "staged-xpis"; - pathShouldntExist(testDir); - - shutdownManager(); - - // Clear commonly set prefs. - try { - Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY); - } catch (e) {} - try { - Services.prefs.clearUserPref(PREF_EM_STRICT_COMPATIBILITY); - } catch (e) {} -}); - -/** - * 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."); - } 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) { - let jData = loadJSON(gExtensionsJSON); - jData.schemaVersion = aNewVersion; - 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"); - } -} - -/** - * A promise-based variant of AddonManager.getAddonsByIDs. - * - * @param {array} list As the first argument of AddonManager.getAddonsByIDs - * @return {promise} - * @resolve {array} The list of add-ons sent by AddonManaget.getAddonsByIDs to - * its callback. - */ -function promiseAddonsByIDs(list) { - return new Promise(resolve => AddonManager.getAddonsByIDs(list, resolve)); -} - -/** - * A promise-based variant of AddonManager.getAddonByID. - * - * @param {string} aId The ID of the add-on. - * @return {promise} - * @resolve {AddonWrapper} The corresponding add-on, or null. - */ -function promiseAddonByID(aId) { - return new Promise(resolve => AddonManager.getAddonByID(aId, resolve)); -} - -/** - * A promise-based variant of AddonManager.getAddonsWithOperationsByTypes - * - * @param {array} aTypes The first argument to - * AddonManager.getAddonsWithOperationsByTypes - * @return {promise} - * @resolve {array} The list of add-ons sent by - * AddonManaget.getAddonsWithOperationsByTypes to its callback. - */ -function promiseAddonsWithOperationsByTypes(aTypes) { - return new Promise(resolve => AddonManager.getAddonsWithOperationsByTypes(aTypes, resolve)); -} - -/** - * Returns a promise that will be resolved when an add-on update check is - * complete. The value resolved will be an AddonInstall if a new version was - * found. - */ -function promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) { - return new Promise((resolve, reject) => { - addon.findUpdates({ - install: null, - - onUpdateAvailable: function(addon, install) { - this.install = install; - }, - - onUpdateFinished: function(addon, error) { - if (error == AddonManager.UPDATE_STATUS_NO_ERROR) - resolve(this.install); - else - reject(error); - } - }, reason); - }); -} |