summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2018-06-26 17:06:36 -0400
committerMatt A. Tobin <email@mattatobin.com>2018-06-26 17:06:36 -0400
commitcacc5283c009b15fa7cc526f3e16148ec9df30db (patch)
tree33da3dcc4d859d1c82d0b1d7f63eb60791a0c19d /toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm
parentc7abad75208513553e41ac0f984f170ba5fd09a3 (diff)
downloadUXP-cacc5283c009b15fa7cc526f3e16148ec9df30db.tar
UXP-cacc5283c009b15fa7cc526f3e16148ec9df30db.tar.gz
UXP-cacc5283c009b15fa7cc526f3e16148ec9df30db.tar.lz
UXP-cacc5283c009b15fa7cc526f3e16148ec9df30db.tar.xz
UXP-cacc5283c009b15fa7cc526f3e16148ec9df30db.zip
[WebExAM] Remove unused AddonTestUtils.jsm
Diffstat (limited to 'toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm')
-rw-r--r--toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm1231
1 files changed, 0 insertions, 1231 deletions
diff --git a/toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm b/toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm
deleted file mode 100644
index 6422929b1..000000000
--- a/toolkit/mozapps/webextensions/internal/AddonTestUtils.jsm
+++ /dev/null
@@ -1,1231 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-/* eslint "mozilla/no-aArgs": 1 */
-/* eslint "no-unused-vars": [2, {"args": "none", "varsIgnorePattern": "^(Cc|Ci|Cr|Cu|EXPORTED_SYMBOLS)$"}] */
-/* eslint "semi": [2, "always"] */
-/* eslint "valid-jsdoc": [2, {requireReturn: false}] */
-
-var EXPORTED_SYMBOLS = ["AddonTestUtils", "MockAsyncShutdown"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-const CERTDB_CONTRACTID = "@mozilla.org/security/x509certdb;1";
-const CERTDB_CID = Components.ID("{fb0bbc5c-452e-4783-b32c-80124693d871}");
-
-
-Cu.importGlobalProperties(["fetch", "TextEncoder"]);
-
-Cu.import("resource://gre/modules/AsyncShutdown.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
-const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
-
-XPCOMUtils.defineLazyModuleGetter(this, "Extension",
- "resource://gre/modules/Extension.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "rdfService",
- "@mozilla.org/rdf/rdf-service;1", "nsIRDFService");
-XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
- "@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
-
-
-XPCOMUtils.defineLazyGetter(this, "AppInfo", () => {
- let AppInfo = {};
- Cu.import("resource://testing-common/AppInfo.jsm", AppInfo);
- return AppInfo;
-});
-
-
-const ArrayBufferInputStream = Components.Constructor(
- "@mozilla.org/io/arraybuffer-input-stream;1",
- "nsIArrayBufferInputStream", "setData");
-
-const nsFile = Components.Constructor(
- "@mozilla.org/file/local;1",
- "nsIFile", "initWithPath");
-
-const RDFXMLParser = Components.Constructor(
- "@mozilla.org/rdf/xml-parser;1",
- "nsIRDFXMLParser", "parseString");
-
-const RDFDataSource = Components.Constructor(
- "@mozilla.org/rdf/datasource;1?name=in-memory-datasource",
- "nsIRDFDataSource");
-
-const ZipReader = Components.Constructor(
- "@mozilla.org/libjar/zip-reader;1",
- "nsIZipReader", "open");
-
-const ZipWriter = Components.Constructor(
- "@mozilla.org/zipwriter;1",
- "nsIZipWriter", "open");
-
-
-// We need some internal bits of AddonManager
-var AMscope = Cu.import("resource://gre/modules/AddonManager.jsm", {});
-var {AddonManager, AddonManagerPrivate} = AMscope;
-
-
-// Mock out AddonManager's reference to the AsyncShutdown module so we can shut
-// down AddonManager from the test
-var MockAsyncShutdown = {
- hook: null,
- status: null,
- profileBeforeChange: {
- addBlocker: function(name, blocker, options) {
- MockAsyncShutdown.hook = blocker;
- MockAsyncShutdown.status = options.fetchState;
- }
- },
- // We can use the real Barrier
- Barrier: AsyncShutdown.Barrier,
-};
-
-AMscope.AsyncShutdown = MockAsyncShutdown;
-
-
-/**
- * Escapes any occurances of &, ", < or > with XML entities.
- *
- * @param {string} str
- * The string to escape.
- * @return {string} The escaped string.
- */
-function escapeXML(str) {
- let replacements = {"&": "&amp;", '"': "&quot;", "'": "&apos;", "<": "&lt;", ">": "&gt;"};
- return String(str).replace(/[&"''<>]/g, m => replacements[m]);
-}
-
-/**
- * A tagged template function which escapes any XML metacharacters in
- * interpolated values.
- *
- * @param {Array<string>} strings
- * An array of literal strings extracted from the templates.
- * @param {Array} values
- * An array of interpolated values extracted from the template.
- * @returns {string}
- * The result of the escaped values interpolated with the literal
- * strings.
- */
-function escaped(strings, ...values) {
- let result = [];
-
- for (let [i, string] of strings.entries()) {
- result.push(string);
- if (i < values.length)
- result.push(escapeXML(values[i]));
- }
-
- return result.join("");
-}
-
-
-class AddonsList {
- constructor(extensionsINI) {
- this.multiprocessIncompatibleIDs = new Set();
-
- if (!extensionsINI.exists()) {
- this.extensions = [];
- this.themes = [];
- return;
- }
-
- let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
- .getService(Ci.nsIINIParserFactory);
-
- let parser = factory.createINIParser(extensionsINI);
-
- function readDirectories(section) {
- var dirs = [];
- var keys = parser.getKeys(section);
- for (let key of XPCOMUtils.IterStringEnumerator(keys)) {
- let descriptor = parser.getString(section, key);
-
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- try {
- file.persistentDescriptor = descriptor;
- } catch (e) {
- // Throws if the directory doesn't exist, we can ignore this since the
- // platform will too.
- continue;
- }
- dirs.push(file);
- }
- return dirs;
- }
-
- this.extensions = readDirectories("ExtensionDirs");
- this.themes = readDirectories("ThemeDirs");
-
- var keys = parser.getKeys("MultiprocessIncompatibleExtensions");
- for (let key of XPCOMUtils.IterStringEnumerator(keys)) {
- let id = parser.getString("MultiprocessIncompatibleExtensions", key);
- this.multiprocessIncompatibleIDs.add(id);
- }
- }
-
- hasItem(type, dir, id) {
- var path = dir.clone();
- path.append(id);
-
- var xpiPath = dir.clone();
- xpiPath.append(`${id}.xpi`);
-
- return this[type].some(file => {
- if (!file.exists())
- throw new Error(`Non-existent path found in extensions.ini: ${file.path}`);
-
- if (file.isDirectory())
- return file.equals(path);
- if (file.isFile())
- return file.equals(xpiPath);
- return false;
- });
- }
-
- isMultiprocessIncompatible(id) {
- return this.multiprocessIncompatibleIDs.has(id);
- }
-
- hasTheme(dir, id) {
- return this.hasItem("themes", dir, id);
- }
-
- hasExtension(dir, id) {
- return this.hasItem("extensions", dir, id);
- }
-}
-
-var AddonTestUtils = {
- addonIntegrationService: null,
- addonsList: null,
- appInfo: null,
- extensionsINI: null,
- testUnpacked: false,
- useRealCertChecks: false,
-
- init(testScope) {
- this.testScope = testScope;
-
- // Get the profile directory for tests to use.
- this.profileDir = testScope.do_get_profile();
-
- this.extensionsINI = this.profileDir.clone();
- this.extensionsINI.append("extensions.ini");
-
- // Register a temporary directory for the tests.
- this.tempDir = this.profileDir.clone();
- this.tempDir.append("temp");
- this.tempDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
- this.registerDirectory("TmpD", this.tempDir);
-
- // Create a replacement app directory for the tests.
- const appDirForAddons = this.profileDir.clone();
- appDirForAddons.append("appdir-addons");
- appDirForAddons.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
- this.registerDirectory("XREAddonAppDir", appDirForAddons);
-
-
- // 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 don't check for hotfixes
- Services.prefs.setCharPref("extensions.hotfix.id", "");
-
- // Ensure signature checks are enabled by default
- Services.prefs.setBoolPref("xpinstall.signatures.required", true);
-
-
- // Write out an empty blocklist.xml file to the profile to ensure nothing
- // is blocklisted by default
- var blockFile = OS.Path.join(this.profileDir.path, "blocklist.xml");
-
- var data = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
- "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">\n" +
- "</blocklist>\n";
-
- this.awaitPromise(OS.File.writeAtomic(blockFile, new TextEncoder().encode(data)));
-
-
- // Make sure that a given path does not exist
- function pathShouldntExist(file) {
- if (file.exists()) {
- throw new Error(`Test cleanup: path ${file.path} exists when it should not`);
- }
- }
-
- testScope.do_register_cleanup(() => {
- for (let file of this.tempXPIs) {
- if (file.exists())
- file.remove(false);
- }
-
- // Check that the temporary directory is empty
- var dirEntries = this.tempDir.directoryEntries
- .QueryInterface(Ci.nsIDirectoryEnumerator);
- var entries = [];
- while (dirEntries.hasMoreElements())
- entries.push(dirEntries.nextFile.leafName);
- if (entries.length)
- throw new Error(`Found unexpected files in temporary directory: ${entries.join(", ")}`);
-
- dirEntries.close();
-
- try {
- appDirForAddons.remove(true);
- } catch (ex) {
- testScope.do_print(`Got exception removing addon app dir: ${ex}`);
- }
-
- // ensure no leftover files in the system addon upgrade location
- let featuresDir = this.profileDir.clone();
- featuresDir.append("features");
- // upgrade directories will be in UUID folders under features/
- let systemAddonDirs = [];
- if (featuresDir.exists()) {
- let featuresDirEntries = featuresDir.directoryEntries
- .QueryInterface(Ci.nsIDirectoryEnumerator);
- while (featuresDirEntries.hasMoreElements()) {
- let entry = featuresDirEntries.getNext();
- entry.QueryInterface(Components.interfaces.nsIFile);
- systemAddonDirs.push(entry);
- }
-
- systemAddonDirs.map(dir => {
- dir.append("stage");
- pathShouldntExist(dir);
- });
- }
-
- // ensure no leftover files in the user addon location
- let testDir = this.profileDir.clone();
- testDir.append("extensions");
- testDir.append("trash");
- pathShouldntExist(testDir);
-
- testDir.leafName = "staged";
- pathShouldntExist(testDir);
-
- return this.promiseShutdownManager();
- });
- },
-
- /**
- * Helper to spin the event loop until a promise resolves or rejects
- *
- * @param {Promise} promise
- * The promise to wait on.
- * @returns {*} The promise's resolution value.
- * @throws The promise's rejection value, if it rejects.
- */
- awaitPromise(promise) {
- let done = false;
- let result;
- let error;
- promise.then(
- val => { result = val; },
- err => { error = err; }
- ).then(() => {
- done = true;
- });
-
- while (!done)
- Services.tm.mainThread.processNextEvent(true);
-
- if (error !== undefined)
- throw error;
- return result;
- },
-
- createAppInfo(ID, name, version, platformVersion = "1.0") {
- AppInfo.updateAppInfo({
- ID, name, version, platformVersion,
- crashReporter: true,
- extraProps: {
- browserTabsRemoteAutostart: false,
- },
- });
- this.appInfo = AppInfo.getAppInfo();
- },
-
- getManifestURI(file) {
- if (file.isDirectory()) {
- file.append("install.rdf");
- if (file.exists()) {
- return NetUtil.newURI(file);
- }
-
- file.leafName = "manifest.json";
- if (file.exists())
- return NetUtil.newURI(file);
-
- throw new Error("No manifest file present");
- }
-
- let zip = ZipReader(file);
- try {
- let uri = NetUtil.newURI(file);
-
- if (zip.hasEntry("install.rdf")) {
- return NetUtil.newURI(`jar:${uri.spec}!/install.rdf`);
- }
-
- if (zip.hasEntry("manifest.json")) {
- return NetUtil.newURI(`jar:${uri.spec}!/manifest.json`);
- }
-
- throw new Error("No manifest file present");
- } finally {
- zip.close();
- }
- },
-
- getIDFromManifest: Task.async(function*(manifestURI) {
- let body = yield fetch(manifestURI.spec);
-
- if (manifestURI.spec.endsWith(".rdf")) {
- let data = yield body.text();
-
- let ds = new RDFDataSource();
- new RDFXMLParser(ds, manifestURI, data);
-
- let rdfID = ds.GetTarget(rdfService.GetResource("urn:mozilla:install-manifest"),
- rdfService.GetResource("http://www.mozilla.org/2004/em-rdf#id"),
- true);
- return rdfID.QueryInterface(Ci.nsIRDFLiteral).Value;
- }
-
- let manifest = yield body.json();
- try {
- return manifest.applications.gecko.id;
- } catch (e) {
- // IDs for WebExtensions are extracted from the certificate when
- // not present in the manifest, so just generate a random one.
- return uuidGen.generateUUID().number;
- }
- }),
-
- overrideCertDB() {
- // Unregister the real database. This only works because the add-ons manager
- // hasn't started up and grabbed the certificate database yet.
- let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
- let factory = registrar.getClassObject(CERTDB_CID, Ci.nsIFactory);
- registrar.unregisterFactory(CERTDB_CID, factory);
-
- // Get the real DB
- let realCertDB = factory.createInstance(null, Ci.nsIX509CertDB);
-
-
- let verifyCert = Task.async(function*(file, result, cert, callback) {
- if (result == Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED &&
- !this.useRealCertChecks && callback.wrappedJSObject) {
- // Bypassing XPConnect allows us to create a fake x509 certificate from JS
- callback = callback.wrappedJSObject;
-
- try {
- let manifestURI = this.getManifestURI(file);
-
- let id = yield this.getIDFromManifest(manifestURI);
-
- let fakeCert = {commonName: id};
-
- return [callback, Cr.NS_OK, fakeCert];
- } catch (e) {
- // If there is any error then just pass along the original results
- } finally {
- // Make sure to close the open zip file or it will be locked.
- if (file.isFile())
- Services.obs.notifyObservers(file, "flush-cache-entry", "cert-override");
- }
- }
-
- return [callback, result, cert];
- }).bind(this);
-
-
- function FakeCertDB() {
- for (let property of Object.keys(realCertDB)) {
- if (property in this)
- continue;
-
- if (typeof realCertDB[property] == "function")
- this[property] = realCertDB[property].bind(realCertDB);
- }
- }
- FakeCertDB.prototype = {
- openSignedAppFileAsync(root, file, callback) {
- // First try calling the real cert DB
- realCertDB.openSignedAppFileAsync(root, file, (result, zipReader, cert) => {
- verifyCert(file.clone(), result, cert, callback)
- .then(([callback, result, cert]) => {
- callback.openSignedAppFileFinished(result, zipReader, cert);
- });
- });
- },
-
- verifySignedDirectoryAsync(root, dir, callback) {
- // First try calling the real cert DB
- realCertDB.verifySignedDirectoryAsync(root, dir, (result, cert) => {
- verifyCert(dir.clone(), result, cert, callback)
- .then(([callback, result, cert]) => {
- callback.verifySignedDirectoryFinished(result, cert);
- });
- });
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIX509CertDB]),
- };
-
- let certDBFactory = XPCOMUtils.generateSingletonFactory(FakeCertDB);
- registrar.registerFactory(CERTDB_CID, "CertDB",
- CERTDB_CONTRACTID, certDBFactory);
- },
-
- /**
- * Starts up the add-on manager as if it was started by the application.
- *
- * @param {boolean} [appChanged = true]
- * 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
- * @returns {Promise}
- * Resolves when the add-on manager's startup has completed.
- */
- promiseStartupManager(appChanged = true) {
- if (this.addonIntegrationService)
- throw new Error("Attempting to startup manager that was already started.");
-
- if (appChanged && this.extensionsINI.exists())
- this.extensionsINI.remove(true);
-
- this.addonIntegrationService = Cc["@mozilla.org/addons/integration;1"]
- .getService(Ci.nsIObserver);
-
- this.addonIntegrationService.observe(null, "addons-startup", null);
-
- this.emit("addon-manager-started");
-
- // Load the add-ons list as it was after extension registration
- this.loadAddonsList();
-
- return Promise.resolve();
- },
-
- promiseShutdownManager() {
- if (!this.addonIntegrationService)
- return Promise.resolve(false);
-
- Services.obs.notifyObservers(null, "quit-application-granted", null);
- return MockAsyncShutdown.hook()
- .then(() => {
- this.emit("addon-manager-shutdown");
-
- this.addonIntegrationService = null;
-
- // Load the add-ons list as it was after application shutdown
- this.loadAddonsList();
-
- // Clear any crash report annotations
- this.appInfo.annotations = {};
-
- // Force the XPIProvider provider to reload to better
- // simulate real-world usage.
- let XPIscope = Cu.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
- let shutdownError = XPIscope.XPIProvider._shutdownError;
-
- AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
- Cu.unload("resource://gre/modules/addons/XPIProvider.jsm");
-
- if (shutdownError)
- throw shutdownError;
-
- return true;
- });
- },
-
- promiseRestartManager(newVersion) {
- return this.promiseShutdownManager()
- .then(() => {
- if (newVersion)
- this.appInfo.version = newVersion;
-
- return this.promiseStartupManager(!!newVersion);
- });
- },
-
- loadAddonsList() {
- this.addonsList = new AddonsList(this.extensionsINI);
- },
-
- /**
- * Creates an update.rdf structure as a string using for the update data passed.
- *
- * @param {Object} data
- * 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 {string} The update.rdf structure as a string.
- */
- createUpdateRDF(data) {
- 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 data) {
- rdf += escaped` <Description about="urn:mozilla:extension:${addon}"><em:updates><Seq>\n`;
-
- for (let versionData of data[addon]) {
- rdf += ' <li><Description>\n';
- rdf += this._writeProps(versionData, ["version", "multiprocessCompatible"],
- ` `);
- for (let app of versionData.targetApplications || []) {
- rdf += " <em:targetApplication><Description>\n";
- rdf += this._writeProps(app, ["id", "minVersion", "maxVersion", "updateLink", "updateHash"],
- ` `);
- rdf += " </Description></em:targetApplication>\n";
- }
- rdf += ' </Description></li>\n';
- }
- rdf += ' </Seq></em:updates></Description>\n';
- }
- rdf += "</RDF>\n";
-
- return rdf;
- },
-
- _writeProps(obj, props, indent = " ") {
- let items = [];
- for (let prop of props) {
- if (prop in obj)
- items.push(escaped`${indent}<em:${prop}>${obj[prop]}</em:${prop}>\n`);
- }
- return items.join("");
- },
-
- _writeArrayProps(obj, props, indent = " ") {
- let items = [];
- for (let prop of props) {
- for (let val of obj[prop] || [])
- items.push(escaped`${indent}<em:${prop}>${val}</em:${prop}>\n`);
- }
- return items.join("");
- },
-
- _writeLocaleStrings(data) {
- let items = [];
-
- items.push(this._writeProps(data, ["name", "description", "creator", "homepageURL"]));
- items.push(this._writeArrayProps(data, ["developer", "translator", "contributor"]));
-
- return items.join("");
- },
-
- createInstallRDF(data) {
- 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';
-
- let props = ["id", "version", "type", "internalName", "updateURL", "updateKey",
- "optionsURL", "optionsType", "aboutURL", "iconURL", "icon64URL",
- "skinnable", "bootstrap", "unpack", "strictCompatibility",
- "multiprocessCompatible", "hasEmbeddedWebExtension"];
- rdf += this._writeProps(data, props);
-
- rdf += this._writeLocaleStrings(data);
-
- for (let platform of data.targetPlatforms || [])
- rdf += escaped`<em:targetPlatform>${platform}</em:targetPlatform>\n`;
-
- for (let app of data.targetApplications || []) {
- rdf += "<em:targetApplication><Description>\n";
- rdf += this._writeProps(app, ["id", "minVersion", "maxVersion"]);
- rdf += "</Description></em:targetApplication>\n";
- }
-
- for (let localized of data.localized || []) {
- rdf += "<em:localized><Description>\n";
- rdf += this._writeArrayProps(localized, ["locale"]);
- rdf += this._writeLocaleStrings(localized);
- rdf += "</Description></em:localized>\n";
- }
-
- for (let dep of data.dependencies || [])
- rdf += escaped`<em:dependency><Description em:id="${dep}"/></em:dependency>\n`;
-
- rdf += "</Description>\n</RDF>\n";
- return rdf;
- },
-
- /**
- * Recursively create all directories upto and including the given
- * path, if they do not exist.
- *
- * @param {string} path The path of the directory to create.
- * @returns {Promise} Resolves when all directories have been created.
- */
- recursiveMakeDir(path) {
- let paths = [];
- for (let lastPath; path != lastPath; lastPath = path, path = OS.Path.dirname(path))
- paths.push(path);
-
- return Promise.all(paths.reverse().map(path =>
- OS.File.makeDir(path, {ignoreExisting: true}).catch(() => {})));
- },
-
- /**
- * Writes the given data to a file in the given zip file.
- *
- * @param {string|nsIFile} zipFile
- * The zip file to write to.
- * @param {Object} files
- * An object containing filenames and the data to write to the
- * corresponding paths in the zip file.
- * @param {integer} [flags = 0]
- * Additional flags to open the file with.
- */
- writeFilesToZip(zipFile, files, flags = 0) {
- if (typeof zipFile == "string")
- zipFile = nsFile(zipFile);
-
- var zipW = ZipWriter(zipFile, FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | flags);
-
- for (let [path, data] of Object.entries(files)) {
- if (!(data instanceof ArrayBuffer))
- data = new TextEncoder("utf-8").encode(data).buffer;
-
- let stream = ArrayBufferInputStream(data, 0, data.byteLength);
-
- // Note these files are being created in the XPI archive with date "0" which is 1970-01-01.
- zipW.addEntryStream(path, 0, Ci.nsIZipWriter.COMPRESSION_NONE,
- stream, false);
- }
-
- zipW.close();
- },
-
- promiseWriteFilesToZip: Task.async(function*(zip, files, flags) {
- yield this.recursiveMakeDir(OS.Path.dirname(zip));
-
- this.writeFilesToZip(zip, files, flags);
-
- return Promise.resolve(nsFile(zip));
- }),
-
- promiseWriteFilesToDir: Task.async(function*(dir, files) {
- yield this.recursiveMakeDir(dir);
-
- for (let [path, data] of Object.entries(files)) {
- path = path.split("/");
- let leafName = path.pop();
-
- // Create parent directories, if necessary.
- let dirPath = dir;
- for (let subDir of path) {
- dirPath = OS.Path.join(dirPath, subDir);
- yield OS.Path.makeDir(dirPath, {ignoreExisting: true});
- }
-
- if (typeof data == "string")
- data = new TextEncoder("utf-8").encode(data);
-
- yield OS.File.writeAtomic(OS.Path.join(dirPath, leafName), data);
- }
-
- return nsFile(dir);
- }),
-
- promiseWriteFilesToExtension(dir, id, files, unpacked = this.testUnpacked) {
- if (typeof files["install.rdf"] === "object")
- files["install.rdf"] = this.createInstallRDF(files["install.rdf"]);
-
- if (unpacked) {
- let path = OS.Path.join(dir, id);
-
- return this.promiseWriteFilesToDir(path, files);
- }
-
- let xpi = OS.Path.join(dir, `${id}.xpi`);
-
- return this.promiseWriteFilesToZip(xpi, files);
- },
-
- tempXPIs: [],
- /**
- * 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 {object} files
- * The object holding data about the add-on
- * @return {nsIFile} A file pointing to the created XPI file
- */
- createTempXPIFile(files) {
- var file = this.tempDir.clone();
- let uuid = uuidGen.generateUUID().number.slice(1, -1);
- file.append(`${uuid}.xpi`);
-
- this.tempXPIs.push(file);
-
- if (typeof files["install.rdf"] === "object")
- files["install.rdf"] = this.createInstallRDF(files["install.rdf"]);
-
- this.writeFilesToZip(file.path, files);
- return file;
- },
-
- /**
- * Creates an XPI file for some WebExtension data in the temporary directory and
- * returns the nsIFile for it. The file will be deleted when the test completes.
- *
- * @param {Object} data
- * The object holding data about the add-on, as expected by
- * |Extension.generateXPI|.
- * @return {nsIFile} A file pointing to the created XPI file
- */
- createTempWebExtensionFile(data) {
- let file = Extension.generateXPI(data);
- this.tempXPIs.push(file);
- return file;
- },
-
- /**
- * Creates an extension proxy file.
- * See: https://developer.mozilla.org/en-US/Add-ons/Setting_up_extension_development_environment#Firefox_extension_proxy_file
- *
- * @param {nsIFile} dir
- * The directory to add the proxy file to.
- * @param {nsIFile} addon
- * An nsIFile for the add-on file that this is a proxy file for.
- * @param {string} id
- * A string to use for the add-on ID.
- * @returns {Promise} Resolves when the file has been created.
- */
- promiseWriteProxyFileToDir(dir, addon, id) {
- let files = {
- [id]: addon.path,
- };
-
- return this.promiseWriteFilesToDir(dir.path, files);
- },
-
- /**
- * 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 {nsIFile} xpiFile
- * The XPI file to install.
- * @param {nsIFile} installLocation
- * The install location (an nsIFile) to install into.
- * @param {string} id
- * The ID to install as.
- * @param {boolean} [unpacked = this.testUnpacked]
- * If true, install as an unpacked directory, rather than a
- * packed XPI.
- * @returns {nsIFile}
- * A file pointing to the installed location of the XPI file or
- * unpacked directory.
- */
- manuallyInstall(xpiFile, installLocation, id, unpacked = this.testUnpacked) {
- if (unpacked) {
- let dir = installLocation.clone();
- dir.append(id);
- dir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
-
- let zip = ZipReader(xpiFile);
- let entries = zip.findEntries(null);
- while (entries.hasMore()) {
- let entry = entries.getNext();
- let target = dir.clone();
- for (let part of entry.split("/"))
- target.append(part);
- zip.extract(entry, target);
- }
- zip.close();
-
- return dir;
- }
-
- let target = installLocation.clone();
- target.append(`${id}.xpi`);
- xpiFile.copyTo(target.parent, target.leafName);
- return target;
- },
-
- /**
- * Manually uninstalls an add-on by removing its files from the install
- * location.
- *
- * @param {nsIFile} installLocation
- * The nsIFile of the install location to remove from.
- * @param {string} id
- * The ID of the add-on to remove.
- * @param {boolean} [unpacked = this.testUnpacked]
- * If true, uninstall an unpacked directory, rather than a
- * packed XPI.
- */
- manuallyUninstall(installLocation, id, unpacked = this.testUnpacked) {
- let file = this.getFileForAddon(installLocation, id, unpacked);
-
- // 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 {nsIFile} dir
- * The nsIFile for the install location
- * @param {string} id
- * The ID of the add-on
- * @param {boolean} [unpacked = this.testUnpacked]
- * If true, return the path to an unpacked directory, rather than a
- * packed XPI.
- * @returns {nsIFile}
- * A file pointing to the XPI file or unpacked directory where
- * the add-on should be installed.
- */
- getFileForAddon(dir, id, unpacked = this.testUnpacked) {
- dir = dir.clone();
- if (unpacked)
- dir.append(id);
- else
- dir.append(`${id}.xpi`);
- return dir;
- },
-
- /**
- * 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 {nsIFile} ext A file pointing to either the packed extension or its unpacked directory.
- * @param {number} time The time to which we set the lastModifiedTime of the extension
- *
- * @deprecated Please use promiseSetExtensionModifiedTime instead
- */
- setExtensionModifiedTime(ext, time) {
- ext.lastModifiedTime = time;
- if (ext.isDirectory()) {
- let entries = ext.directoryEntries
- .QueryInterface(Ci.nsIDirectoryEnumerator);
- while (entries.hasMoreElements())
- this.setExtensionModifiedTime(entries.nextFile, time);
- entries.close();
- }
- },
-
- promiseSetExtensionModifiedTime: Task.async(function*(path, time) {
- yield OS.File.setDates(path, time, time);
-
- let iterator = new OS.File.DirectoryIterator(path);
- try {
- yield iterator.forEach(entry => {
- return this.promiseSetExtensionModifiedTime(entry.path, time);
- });
- } catch (ex) {
- if (ex instanceof OS.File.Error)
- return;
- throw ex;
- } finally {
- iterator.close().catch(() => {});
- }
- }),
-
- registerDirectory(key, dir) {
- var dirProvider = {
- getFile(prop, persistent) {
- persistent.value = false;
- if (prop == key)
- return dir.clone();
- return null;
- },
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]),
- };
- Services.dirsvc.registerProvider(dirProvider);
- },
-
- /**
- * Returns a promise that resolves when the given add-on event is fired. The
- * resolved value is an array of arguments passed for the event.
- *
- * @param {string} event
- * The name of the AddonListener event handler method for which
- * an event is expected.
- * @returns {Promise<Array>}
- * Resolves to an array containing the event handler's
- * arguments the first time it is called.
- */
- promiseAddonEvent(event) {
- return new Promise(resolve => {
- let listener = {
- [event](...args) {
- AddonManager.removeAddonListener(listener);
- resolve(args);
- },
- };
-
- AddonManager.addAddonListener(listener);
- });
- },
-
- /**
- * A helper method to install AddonInstall and wait for completion.
- *
- * @param {AddonInstall} install
- * The add-on to install.
- * @returns {Promise}
- * Resolves when the install completes, either successfully or
- * in failure.
- */
- promiseCompleteInstall(install) {
- let listener;
- return new Promise(resolve => {
- listener = {
- onDownloadFailed: resolve,
- onDownloadCancelled: resolve,
- onInstallFailed: resolve,
- onInstallCancelled: resolve,
- onInstallEnded: resolve,
- onInstallPostponed: resolve,
- };
-
- install.addListener(listener);
- install.install();
- }).then(() => {
- install.removeListener(listener);
- });
- },
-
- /**
- * A helper method to install a file.
- *
- * @param {nsIFile} file
- * The file to install
- * @param {boolean} [ignoreIncompatible = false]
- * Optional parameter to ignore add-ons that are incompatible
- * with the application
- * @returns {Promise}
- * Resolves when the install has completed.
- */
- promiseInstallFile(file, ignoreIncompatible = false) {
- return new Promise((resolve, reject) => {
- AddonManager.getInstallForFile(file, install => {
- if (!install)
- reject(new Error(`No AddonInstall created for ${file.path}`));
- else if (install.state != AddonManager.STATE_DOWNLOADED)
- reject(new Error(`Expected file to be downloaded for install of ${file.path}`));
- else if (ignoreIncompatible && install.addon.appDisabled)
- resolve();
- else
- resolve(this.promiseCompleteInstall(install));
- });
- });
- },
-
- /**
- * A helper method to install an array of files.
- *
- * @param {Iterable<nsIFile>} files
- * The files to install
- * @param {boolean} [ignoreIncompatible = false]
- * Optional parameter to ignore add-ons that are incompatible
- * with the application
- * @returns {Promise}
- * Resolves when the installs have completed.
- */
- promiseInstallAllFiles(files, ignoreIncompatible = false) {
- return Promise.all(Array.from(
- files,
- file => this.promiseInstallFile(file, ignoreIncompatible)));
- },
-
- promiseCompleteAllInstalls(installs) {
- return Promise.all(Array.from(installs, this.promiseCompleteInstall));
- },
-
- /**
- * A promise-based variant of AddonManager.getAddonsByIDs.
- *
- * @param {Array<string>} list
- * As the first argument of AddonManager.getAddonsByIDs
- * @return {Promise<Array<Addon>>}
- * Resolves to the array of add-ons for the given IDs.
- */
- promiseAddonsByIDs(list) {
- return new Promise(resolve => AddonManager.getAddonsByIDs(list, resolve));
- },
-
- /**
- * A promise-based variant of AddonManager.getAddonByID.
- *
- * @param {string} id
- * The ID of the add-on.
- * @return {Promise<Addon>}
- * Resolves to the add-on with the given ID.
- */
- promiseAddonByID(id) {
- return new Promise(resolve => AddonManager.getAddonByID(id, 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.
- *
- * @param {object} addon The add-on to find updates for.
- * @param {integer} reason The type of update to find.
- * @return {Promise<object>} an object containing information about the update.
- */
- promiseFindAddonUpdates(addon, reason = AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
- let equal = this.testScope.equal;
- return new Promise((resolve, reject) => {
- let result = {};
- addon.findUpdates({
- onNoCompatibilityUpdateAvailable: function(addon2) {
- if ("compatibilityUpdate" in result) {
- throw new Error("Saw multiple compatibility update events");
- }
- equal(addon, addon2, "onNoCompatibilityUpdateAvailable");
- result.compatibilityUpdate = false;
- },
-
- onCompatibilityUpdateAvailable: function(addon2) {
- if ("compatibilityUpdate" in result) {
- throw new Error("Saw multiple compatibility update events");
- }
- equal(addon, addon2, "onCompatibilityUpdateAvailable");
- result.compatibilityUpdate = true;
- },
-
- onNoUpdateAvailable: function(addon2) {
- if ("updateAvailable" in result) {
- throw new Error("Saw multiple update available events");
- }
- equal(addon, addon2, "onNoUpdateAvailable");
- result.updateAvailable = false;
- },
-
- onUpdateAvailable: function(addon2, install) {
- if ("updateAvailable" in result) {
- throw new Error("Saw multiple update available events");
- }
- equal(addon, addon2, "onUpdateAvailable");
- result.updateAvailable = install;
- },
-
- onUpdateFinished: function(addon2, error) {
- equal(addon, addon2, "onUpdateFinished");
- if (error == AddonManager.UPDATE_STATUS_NO_ERROR) {
- resolve(result);
- } else {
- result.error = error;
- reject(result);
- }
- }
- }, reason);
- });
- },
-
- /**
- * A promise-based variant of AddonManager.getAddonsWithOperationsByTypes
- *
- * @param {Array<string>} types
- * The first argument to AddonManager.getAddonsWithOperationsByTypes
- * @return {Promise<Array<Addon>>}
- * Resolves to an array of add-ons with the given operations
- * pending.
- */
- promiseAddonsWithOperationsByTypes(types) {
- return new Promise(resolve => AddonManager.getAddonsWithOperationsByTypes(types, resolve));
- },
-
- /**
- * Monitors console output for the duration of a task, and returns a promise
- * which resolves to a tuple containing a list of all console messages
- * generated during the task's execution, and the result of the task itself.
- *
- * @param {function} task
- * The task to run while monitoring console output. May be
- * either a generator function, per Task.jsm, or an ordinary
- * function which returns promose.
- * @return {Promise<[Array<nsIConsoleMessage>, *]>}
- * Resolves to an object containing a `messages` property, with
- * the array of console messages emitted during the execution
- * of the task, and a `result` property, containing the task's
- * return value.
- */
- promiseConsoleOutput: Task.async(function*(task) {
- const DONE = "=== xpcshell test console listener done ===";
-
- let listener, messages = [];
- let awaitListener = new Promise(resolve => {
- listener = msg => {
- if (msg == DONE) {
- resolve();
- } else {
- msg instanceof Ci.nsIScriptError;
- messages.push(msg);
- }
- };
- });
-
- Services.console.registerListener(listener);
- try {
- let result = yield task();
-
- Services.console.logStringMessage(DONE);
- yield awaitListener;
-
- return {messages, result};
- } finally {
- Services.console.unregisterListener(listener);
- }
- }),
-};
-
-for (let [key, val] of Object.entries(AddonTestUtils)) {
- if (typeof val == "function")
- AddonTestUtils[key] = val.bind(AddonTestUtils);
-}
-
-EventEmitter.decorate(AddonTestUtils);