summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/plugins/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/plugins/head.js')
-rw-r--r--browser/base/content/test/plugins/head.js452
1 files changed, 452 insertions, 0 deletions
diff --git a/browser/base/content/test/plugins/head.js b/browser/base/content/test/plugins/head.js
new file mode 100644
index 0000000000..d00bd4a446
--- /dev/null
+++ b/browser/base/content/test/plugins/head.js
@@ -0,0 +1,452 @@
+var { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm"
+);
+ChromeUtils.defineModuleGetter(
+ this,
+ "PromiseUtils",
+ "resource://gre/modules/PromiseUtils.jsm"
+);
+
+XPCOMUtils.defineLazyServiceGetters(this, {
+ uuidGen: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
+});
+
+// Various tests in this directory may define gTestBrowser, to use as the
+// default browser under test in some of the functions below.
+/* global gTestBrowser:true */
+
+/**
+ * Waits a specified number of miliseconds.
+ *
+ * Usage:
+ * let wait = yield waitForMs(2000);
+ * ok(wait, "2 seconds should now have elapsed");
+ *
+ * @param aMs the number of miliseconds to wait for
+ * @returns a Promise that resolves to true after the time has elapsed
+ */
+function waitForMs(aMs) {
+ return new Promise(resolve => {
+ setTimeout(done, aMs);
+ function done() {
+ resolve(true);
+ }
+ });
+}
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ if (url) {
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+ }
+
+ return loaded;
+}
+
+function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) {
+ let tries = 0;
+ let maxTries = aTries || 100; // 100 tries
+ let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds
+ let interval = setInterval(function() {
+ if (tries >= maxTries) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ let conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ ok(false, e + "\n" + e.stack);
+ conditionPassed = false;
+ }
+ if (conditionPassed) {
+ moveOn();
+ }
+ tries++;
+ }, maxWait);
+ let moveOn = function() {
+ clearInterval(interval);
+ nextTest();
+ };
+}
+
+// Waits for a conditional function defined by the caller to return true.
+function promiseForCondition(aConditionFn, aMessage, aTries, aWait) {
+ return new Promise(resolve => {
+ waitForCondition(
+ aConditionFn,
+ resolve,
+ aMessage || "Condition didn't pass.",
+ aTries,
+ aWait
+ );
+ });
+}
+
+// Returns the chrome side nsIPluginTag for this plugin
+function getTestPlugin(aName) {
+ let pluginName = aName || "Test Plug-in";
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (let i = 0; i < tags.length; i++) {
+ if (tags[i].name == pluginName) {
+ return tags[i];
+ }
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
+// Set the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+ let name = pluginName || "Test Plug-in";
+ let plugin = getTestPlugin(name);
+ plugin.enabledState = newEnabledState;
+}
+
+// Get the 'enabledState' on the nsIPluginTag stored in the main or chrome
+// process.
+function getTestPluginEnabledState(pluginName) {
+ let name = pluginName || "Test Plug-in";
+ let plugin = getTestPlugin(name);
+ return plugin.enabledState;
+}
+
+// Returns a promise for nsIObjectLoadingContent props data.
+function promiseForPluginInfo(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return SpecialPowers.spawn(browser, [aId], async function(contentId) {
+ let plugin = content.document.getElementById(contentId);
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent)) {
+ throw new Error("no plugin found");
+ }
+ return {
+ pluginFallbackType: plugin.pluginFallbackType,
+ activated: plugin.activated,
+ hasRunningPlugin: plugin.hasRunningPlugin,
+ displayedType: plugin.displayedType,
+ };
+ });
+}
+
+// Return a promise and call the plugin's playPlugin() method.
+function promisePlayObject(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return SpecialPowers.spawn(browser, [aId], async function(contentId) {
+ content.document.getElementById(contentId).playPlugin();
+ });
+}
+
+function promiseCrashObject(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return SpecialPowers.spawn(browser, [aId], async function(contentId) {
+ let plugin = content.document.getElementById(contentId);
+ Cu.waiveXrays(plugin).crash();
+ });
+}
+
+// Return a promise and call the plugin's getObjectValue() method.
+function promiseObjectValueResult(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return SpecialPowers.spawn(browser, [aId], async function(contentId) {
+ let plugin = content.document.getElementById(contentId);
+ return Cu.waiveXrays(plugin).getObjectValue();
+ });
+}
+
+// Return a promise and reload the target plugin in the page
+function promiseReloadPlugin(aId, aBrowser) {
+ let browser = aBrowser || gTestBrowser;
+ return SpecialPowers.spawn(browser, [aId], async function(contentId) {
+ let plugin = content.document.getElementById(contentId);
+ // eslint-disable-next-line no-self-assign
+ plugin.src = plugin.src;
+ });
+}
+
+// after a test is done using the plugin doorhanger, we should just clear
+// any permissions that may have crept in
+function clearAllPluginPermissions() {
+ for (let perm of Services.perms.all) {
+ if (perm.type.startsWith("plugin")) {
+ info(
+ "removing permission:" + perm.principal.origin + " " + perm.type + "\n"
+ );
+ Services.perms.removePermission(perm);
+ }
+ }
+}
+
+// Ported from AddonTestUtils.jsm
+let JSONBlocklistWrapper = {
+ /**
+ * Load the data from the specified files into the *real* blocklist providers.
+ * Loads using loadBlocklistRawData, which will treat this as an update.
+ *
+ * @param {nsIFile} dir
+ * The directory in which the files live.
+ * @param {string} prefix
+ * a prefix for the files which ought to be loaded.
+ * This method will suffix -extensions.json and -plugins.json
+ * to the prefix it is given, and attempt to load both.
+ * Insofar as either exists, their data will be dumped into
+ * the respective store, and the respective update handlers
+ * will be called.
+ */
+ async loadBlocklistData(url) {
+ const fullURL = `${url}-plugins.json`;
+ let jsonObj;
+ try {
+ jsonObj = await (await fetch(fullURL)).json();
+ } catch (ex) {
+ ok(false, ex);
+ }
+ info(`Loaded ${fullURL}`);
+
+ return this.loadBlocklistRawData({ plugins: jsonObj });
+ },
+
+ /**
+ * Load the following data into the *real* blocklist providers.
+ * While `overrideBlocklist` replaces the blocklist entirely with a mock
+ * that returns dummy data, this method instead loads data into the actual
+ * blocklist, fires update methods as would happen if this data came from
+ * an actual blocklist update, etc.
+ *
+ * @param {object} data
+ * An object that can optionally have `extensions` and/or `plugins`
+ * properties, each being an array of blocklist items.
+ * This code only uses plugin blocks, that can look something like:
+ *
+ * {
+ * "matchFilename": "libnptest\\.so|nptest\\.dll|Test\\.plugin",
+ * "versionRange": [
+ * {
+ * "severity": "0",
+ * "vulnerabilityStatus": "1"
+ * }
+ * ],
+ * "blockID": "p9999"
+ * }
+ *
+ */
+ async loadBlocklistRawData(data) {
+ const bsPass = ChromeUtils.import(
+ "resource://gre/modules/Blocklist.jsm",
+ null
+ );
+ const blocklistMapping = {
+ extensions: bsPass.ExtensionBlocklistRS,
+ plugins: bsPass.PluginBlocklistRS,
+ };
+
+ for (const [dataProp, blocklistObj] of Object.entries(blocklistMapping)) {
+ let newData = data[dataProp];
+ if (!newData) {
+ continue;
+ }
+ if (!Array.isArray(newData)) {
+ throw new Error(
+ "Expected an array of new items to put in the " +
+ dataProp +
+ " blocklist!"
+ );
+ }
+ for (let item of newData) {
+ if (!item.id) {
+ item.id = uuidGen.generateUUID().number.slice(1, -1);
+ }
+ if (!item.last_modified) {
+ item.last_modified = Date.now();
+ }
+ }
+ await blocklistObj.ensureInitialized();
+ let db = await blocklistObj._client.db;
+ await db.importChanges({}, 42, newData, { clear: true });
+ // We manually call _onUpdate... which is evil, but at the moment kinto doesn't have
+ // a better abstraction unless you want to mock your own http server to do the update.
+ await blocklistObj._onUpdate();
+ }
+ },
+};
+
+// An async helper that insures a new blocklist is loaded (in both
+// processes if applicable).
+async function asyncSetAndUpdateBlocklist(aURL, aBrowser) {
+ let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false;
+ let localPromise = TestUtils.topicObserved("plugin-blocklist-updated");
+ info("*** loading blocklist: " + aURL);
+ await JSONBlocklistWrapper.loadBlocklistData(aURL);
+ info("*** waiting on local load");
+ await localPromise;
+ if (doTestRemote) {
+ info("*** waiting on remote load");
+ // Ensure content has been updated with the blocklist
+ await SpecialPowers.spawn(aBrowser, [], () => {});
+ }
+ info("*** blocklist loaded.");
+}
+
+// Insure there's a popup notification present. This test does not indicate
+// open state. aBrowser can be undefined.
+function promisePopupNotification(aName, aBrowser) {
+ return new Promise(resolve => {
+ waitForCondition(
+ () => PopupNotifications.getNotification(aName, aBrowser),
+ () => {
+ ok(
+ !!PopupNotifications.getNotification(aName, aBrowser),
+ aName + " notification appeared"
+ );
+
+ resolve();
+ },
+ "timeout waiting for popup notification " + aName
+ );
+ });
+}
+
+/**
+ * Allows setting focus on a window, and waiting for that window to achieve
+ * focus.
+ *
+ * @param aWindow
+ * The window to focus and wait for.
+ *
+ * @return {Promise}
+ * @resolves When the window is focused.
+ * @rejects Never.
+ */
+function promiseWaitForFocus(aWindow) {
+ return new Promise(resolve => {
+ waitForFocus(resolve, aWindow);
+ });
+}
+
+/**
+ * Returns a Promise that resolves when a notification bar
+ * for a browser is shown. Alternatively, for old-style callers,
+ * can automatically call a callback before it resolves.
+ *
+ * @param notificationID
+ * The ID of the notification to look for.
+ * @param browser
+ * The browser to check for the notification bar.
+ * @param callback (optional)
+ * A function to be called just before the Promise resolves.
+ *
+ * @return Promise
+ */
+function waitForNotificationBar(notificationID, browser, callback) {
+ return new Promise((resolve, reject) => {
+ let notification;
+ let notificationBox = gBrowser.getNotificationBox(browser);
+ waitForCondition(
+ () =>
+ (notification = notificationBox.getNotificationWithValue(
+ notificationID
+ )),
+ () => {
+ ok(
+ notification,
+ `Successfully got the ${notificationID} notification bar`
+ );
+ if (callback) {
+ callback(notification);
+ }
+ resolve(notification);
+ },
+ `Waited too long for the ${notificationID} notification bar`
+ );
+ });
+}
+
+function promiseForNotificationBar(notificationID, browser) {
+ return new Promise(resolve => {
+ waitForNotificationBar(notificationID, browser, resolve);
+ });
+}
+
+/**
+ * Reshow a notification and call a callback when it is reshown.
+ * @param notification
+ * The notification to reshow
+ * @param callback
+ * A function to be called when the notification has been reshown
+ */
+function waitForNotificationShown(notification, callback) {
+ if (PopupNotifications.panel.state == "open") {
+ executeSoon(callback);
+ return;
+ }
+ PopupNotifications.panel.addEventListener(
+ "popupshown",
+ function(e) {
+ callback();
+ },
+ { once: true }
+ );
+ notification.reshow();
+}
+
+function promiseForNotificationShown(notification) {
+ return new Promise(resolve => {
+ waitForNotificationShown(notification, resolve);
+ });
+}
+
+/**
+ * Due to layout being async, "PluginBindAttached" may trigger later. This
+ * returns a Promise that resolves once we've forced a layout flush, which
+ * triggers the PluginBindAttached event to fire. This trick only works if
+ * there is some sort of plugin in the page.
+ * @param browser
+ * The browser to force plugin bindings in.
+ * @return Promise
+ */
+function promiseUpdatePluginBindings(browser) {
+ return SpecialPowers.spawn(browser, [], async function() {
+ let doc = content.document;
+ let elems = doc.getElementsByTagName("embed");
+ if (!elems || elems.length < 1) {
+ elems = doc.getElementsByTagName("object");
+ }
+ if (elems && elems.length) {
+ elems[0].clientTop;
+ }
+ });
+}