mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-20 16:55:40 +00:00
f9f5914039
# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D36041 --HG-- extra : source : 96b3895a3b2aa2fcb064c85ec5857b7216884556
482 lines
14 KiB
JavaScript
482 lines
14 KiB
JavaScript
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 */
|
|
|
|
/**
|
|
* 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 ContentTask.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 nsIObjectLoadingContent
|
|
// playPlugin() method.
|
|
function promisePlayObject(aId, aBrowser) {
|
|
let browser = aBrowser || gTestBrowser;
|
|
return ContentTask.spawn(browser, aId, async function(contentId) {
|
|
let plugin = content.document.getElementById(contentId);
|
|
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
|
objLoadingContent.playPlugin();
|
|
});
|
|
}
|
|
|
|
function promiseCrashObject(aId, aBrowser) {
|
|
let browser = aBrowser || gTestBrowser;
|
|
return ContentTask.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 ContentTask.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 ContentTask.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() {
|
|
let perms = Services.perms.enumerator;
|
|
while (perms.hasMoreElements()) {
|
|
let perm = perms.getNext();
|
|
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 collection = await blocklistObj._client.openCollection();
|
|
await collection.clear();
|
|
await collection.loadDump(newData);
|
|
// 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).
|
|
var _originalTestBlocklistURL = null;
|
|
async function asyncSetAndUpdateBlocklist(aURL, aBrowser) {
|
|
let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false;
|
|
if (!_originalTestBlocklistURL) {
|
|
_originalTestBlocklistURL = Services.prefs.getCharPref(
|
|
"extensions.blocklist.url"
|
|
);
|
|
}
|
|
let localPromise = TestUtils.topicObserved("plugin-blocklist-updated");
|
|
if (Services.prefs.getBoolPref("extensions.blocklist.useXML", true)) {
|
|
info("*** loading new blocklist: " + aURL + ".xml");
|
|
Services.prefs.setCharPref("extensions.blocklist.url", aURL + ".xml");
|
|
let blocklistNotifier = Cc[
|
|
"@mozilla.org/extensions/blocklist;1"
|
|
].getService(Ci.nsITimerCallback);
|
|
blocklistNotifier.notify(null);
|
|
} else {
|
|
info("*** loading blocklist using json: " + 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 ContentTask.spawn(aBrowser, null, () => {});
|
|
}
|
|
info("*** blocklist loaded.");
|
|
}
|
|
|
|
// Reset back to the blocklist we had at the start of the test run.
|
|
function resetBlocklist() {
|
|
Services.prefs.setCharPref(
|
|
"extensions.blocklist.url",
|
|
_originalTestBlocklistURL
|
|
);
|
|
}
|
|
|
|
// 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 ContentTask.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 > 0) {
|
|
elems[0].clientTop;
|
|
}
|
|
});
|
|
}
|