summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/head.js')
-rw-r--r--toolkit/components/extensions/test/xpcshell/head.js354
1 files changed, 354 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/head.js b/toolkit/components/extensions/test/xpcshell/head.js
new file mode 100644
index 0000000000..10be62a7e2
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/head.js
@@ -0,0 +1,354 @@
+"use strict";
+/* exported createHttpServer, cleanupDir, clearCache, optionalPermissionsPromptHandler, promiseConsoleOutput,
+ promiseQuotaManagerServiceReset, promiseQuotaManagerServiceClear,
+ runWithPrefs, testEnv, withHandlingUserInput, resetHandlingUserInput,
+ assertPersistentListeners, promiseExtensionEvent, assertHasPersistedScriptsCachedFlag,
+ assertIsPersistedScriptsCachedFlag
+*/
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var {
+ clearInterval,
+ clearTimeout,
+ setInterval,
+ setIntervalWithTarget,
+ setTimeout,
+ setTimeoutWithTarget,
+} = ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs");
+var { AddonTestUtils, MockAsyncShutdown } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ ContentTask: "resource://testing-common/ContentTask.sys.mjs",
+ Extension: "resource://gre/modules/Extension.sys.mjs",
+ ExtensionData: "resource://gre/modules/Extension.sys.mjs",
+ ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
+ ExtensionTestUtils:
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs",
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+ MessageChannel: "resource://testing-common/MessageChannel.sys.mjs",
+ PromiseTestUtils: "resource://testing-common/PromiseTestUtils.sys.mjs",
+ Schemas: "resource://gre/modules/Schemas.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+});
+
+PromiseTestUtils.allowMatchingRejectionsGlobally(
+ /Message manager disconnected/
+);
+
+// Persistent Listener test functionality
+const { assertPersistentListeners } = ExtensionTestUtils.testAssertions;
+
+// https_first automatically upgrades http to https, but the tests are not
+// designed to expect that. And it is not easy to change that because
+// nsHttpServer does not support https (bug 1742061). So disable https_first.
+Services.prefs.setBoolPref("dom.security.https_first", false);
+
+// These values may be changed in later head files and tested in check_remote
+// below.
+Services.prefs.setBoolPref("browser.tabs.remote.autostart", false);
+Services.prefs.setBoolPref("extensions.webextensions.remote", false);
+const testEnv = {
+ expectRemote: false,
+};
+
+add_setup(function check_remote() {
+ Assert.equal(
+ WebExtensionPolicy.useRemoteWebExtensions,
+ testEnv.expectRemote,
+ "useRemoteWebExtensions matches"
+ );
+ Assert.equal(
+ WebExtensionPolicy.isExtensionProcess,
+ !testEnv.expectRemote,
+ "testing from extension process"
+ );
+});
+
+ExtensionTestUtils.init(this);
+
+var createHttpServer = (...args) => {
+ AddonTestUtils.maybeInit(this);
+ return AddonTestUtils.createHttpServer(...args);
+};
+
+if (AppConstants.platform === "android") {
+ Services.io.offline = true;
+}
+
+/**
+ * Clears the HTTP and content image caches.
+ */
+function clearCache() {
+ Services.cache2.clear();
+
+ let imageCache = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools)
+ .getImgCacheForDocument(null);
+ imageCache.clearCache(false);
+}
+
+var promiseConsoleOutput = async function (task) {
+ const DONE = `=== console listener ${Math.random()} done ===`;
+
+ let listener;
+ let messages = [];
+ let awaitListener = new Promise(resolve => {
+ listener = msg => {
+ if (msg == DONE) {
+ resolve();
+ } else {
+ void (msg instanceof Ci.nsIConsoleMessage);
+ void (msg instanceof Ci.nsIScriptError);
+ messages.push(msg);
+ }
+ };
+ });
+
+ Services.console.registerListener(listener);
+ try {
+ let result = await task();
+
+ Services.console.logStringMessage(DONE);
+ await awaitListener;
+
+ return { messages, result };
+ } finally {
+ Services.console.unregisterListener(listener);
+ }
+};
+
+// Attempt to remove a directory. If the Windows OS is still using the
+// file sometimes remove() will fail. So try repeatedly until we can
+// remove it or we give up.
+function cleanupDir(dir) {
+ let count = 0;
+ return new Promise((resolve, reject) => {
+ function tryToRemoveDir() {
+ count += 1;
+ try {
+ dir.remove(true);
+ } catch (e) {
+ // ignore
+ }
+ if (!dir.exists()) {
+ return resolve();
+ }
+ if (count >= 25) {
+ return reject(`Failed to cleanup directory: ${dir}`);
+ }
+ setTimeout(tryToRemoveDir, 100);
+ }
+ tryToRemoveDir();
+ });
+}
+
+// Run a test with the specified preferences and then restores their initial values
+// right after the test function run (whether it passes or fails).
+async function runWithPrefs(prefsToSet, testFn) {
+ const setPrefs = prefs => {
+ for (let [pref, value] of prefs) {
+ if (value === undefined) {
+ // Clear any pref that didn't have a user value.
+ info(`Clearing pref "${pref}"`);
+ Services.prefs.clearUserPref(pref);
+ continue;
+ }
+
+ info(`Setting pref "${pref}": ${value}`);
+ switch (typeof value) {
+ case "boolean":
+ Services.prefs.setBoolPref(pref, value);
+ break;
+ case "number":
+ Services.prefs.setIntPref(pref, value);
+ break;
+ case "string":
+ Services.prefs.setStringPref(pref, value);
+ break;
+ default:
+ throw new Error("runWithPrefs doesn't support this pref type yet");
+ }
+ }
+ };
+
+ const getPrefs = prefs => {
+ return prefs.map(([pref, value]) => {
+ info(`Getting initial pref value for "${pref}"`);
+ if (!Services.prefs.prefHasUserValue(pref)) {
+ // Check if the pref doesn't have a user value.
+ return [pref, undefined];
+ }
+ switch (typeof value) {
+ case "boolean":
+ return [pref, Services.prefs.getBoolPref(pref)];
+ case "number":
+ return [pref, Services.prefs.getIntPref(pref)];
+ case "string":
+ return [pref, Services.prefs.getStringPref(pref)];
+ default:
+ throw new Error("runWithPrefs doesn't support this pref type yet");
+ }
+ });
+ };
+
+ let initialPrefsValues = [];
+
+ try {
+ initialPrefsValues = getPrefs(prefsToSet);
+
+ setPrefs(prefsToSet);
+
+ await testFn();
+ } finally {
+ info("Restoring initial preferences values on exit");
+ setPrefs(initialPrefsValues);
+ }
+}
+
+// "Handling User Input" test helpers.
+
+let extensionHandlers = new WeakSet();
+
+function handlingUserInputFrameScript() {
+ /* globals content */
+ // eslint-disable-next-line no-shadow
+ const { MessageChannel } = ChromeUtils.importESModule(
+ "resource://testing-common/MessageChannel.sys.mjs"
+ );
+
+ let handle;
+ MessageChannel.addListener(this, "ExtensionTest:HandleUserInput", {
+ receiveMessage({ name, data }) {
+ if (data) {
+ handle = content.windowUtils.setHandlingUserInput(true);
+ } else if (handle) {
+ handle.destruct();
+ handle = null;
+ }
+ },
+ });
+}
+
+// If you use withHandlingUserInput then restart the addon manager,
+// you need to reset this before using withHandlingUserInput again.
+function resetHandlingUserInput() {
+ extensionHandlers = new WeakSet();
+}
+
+async function withHandlingUserInput(extension, fn) {
+ let { messageManager } = extension.extension.groupFrameLoader;
+
+ if (!extensionHandlers.has(extension)) {
+ messageManager.loadFrameScript(
+ `data:,(${encodeURI(handlingUserInputFrameScript)}).call(this)`,
+ false,
+ true
+ );
+ extensionHandlers.add(extension);
+ }
+
+ await MessageChannel.sendMessage(
+ messageManager,
+ "ExtensionTest:HandleUserInput",
+ true
+ );
+ await fn();
+ await MessageChannel.sendMessage(
+ messageManager,
+ "ExtensionTest:HandleUserInput",
+ false
+ );
+}
+
+// QuotaManagerService test helpers.
+
+function promiseQuotaManagerServiceReset() {
+ info("Calling QuotaManagerService.reset to enforce new test storage limits");
+ return new Promise(resolve => {
+ Services.qms.reset().callback = resolve;
+ });
+}
+
+function promiseQuotaManagerServiceClear() {
+ info(
+ "Calling QuotaManagerService.clear to empty the test data and refresh test storage limits"
+ );
+ return new Promise(resolve => {
+ Services.qms.clear().callback = resolve;
+ });
+}
+
+// Optional Permission prompt handling
+const optionalPermissionsPromptHandler = {
+ sawPrompt: false,
+ acceptPrompt: false,
+
+ init() {
+ Services.prefs.setBoolPref(
+ "extensions.webextOptionalPermissionPrompts",
+ true
+ );
+ Services.obs.addObserver(this, "webextension-optional-permission-prompt");
+ registerCleanupFunction(() => {
+ Services.obs.removeObserver(
+ this,
+ "webextension-optional-permission-prompt"
+ );
+ Services.prefs.clearUserPref(
+ "extensions.webextOptionalPermissionPrompts"
+ );
+ });
+ },
+
+ observe(subject, topic, data) {
+ if (topic == "webextension-optional-permission-prompt") {
+ this.sawPrompt = true;
+ let { resolve } = subject.wrappedJSObject;
+ resolve(this.acceptPrompt);
+ }
+ },
+};
+
+function promiseExtensionEvent(wrapper, event) {
+ return new Promise(resolve => {
+ wrapper.extension.once(event, (...args) => resolve(args));
+ });
+}
+
+async function assertHasPersistedScriptsCachedFlag(ext) {
+ const { StartupCache } = ExtensionParent;
+ const allCachedGeneral = StartupCache._data.get("general");
+ equal(
+ allCachedGeneral
+ .get(ext.id)
+ ?.get(ext.version)
+ ?.get("scripting")
+ ?.has("hasPersistedScripts"),
+ true,
+ "Expect the StartupCache to include hasPersistedScripts flag"
+ );
+}
+
+async function assertIsPersistentScriptsCachedFlag(ext, expectedValue) {
+ const { StartupCache } = ExtensionParent;
+ const allCachedGeneral = StartupCache._data.get("general");
+ equal(
+ allCachedGeneral
+ .get(ext.id)
+ ?.get(ext.version)
+ ?.get("scripting")
+ ?.get("hasPersistedScripts"),
+ expectedValue,
+ "Expected cached value set on hasPersistedScripts flag"
+ );
+}