Files
tubestation/browser/base/content/test/webextensions/head.js
Andrew Swan d5b099ef8d Bug 1342142 Part 2 Enable webextension permission prompts r=mossop
MozReview-Commit-ID: 5p4lvWXn9Hh
2017-03-10 16:05:26 -08:00

380 lines
13 KiB
JavaScript

const BASE = getRootDirectory(gTestPath)
.replace("chrome://mochitests/content/", "https://example.com/");
Cu.import("resource:///modules/ExtensionsUI.jsm");
/**
* Wait for the given PopupNotification to display
*
* @param {string} name
* The name of the notification to wait for.
*
* @returns {Promise}
* Resolves with the notification window.
*/
function promisePopupNotificationShown(name) {
return new Promise(resolve => {
function popupshown() {
let notification = PopupNotifications.getNotification(name);
if (!notification) { return; }
ok(notification, `${name} notification shown`);
ok(PopupNotifications.isPanelOpen, "notification panel open");
PopupNotifications.panel.removeEventListener("popupshown", popupshown);
resolve(PopupNotifications.panel.firstChild);
}
PopupNotifications.panel.addEventListener("popupshown", popupshown);
});
}
/**
* Wait for a specific install event to fire for a given addon
*
* @param {AddonWrapper} addon
* The addon to watch for an event on
* @param {string}
* The name of the event to watch for (e.g., onInstallEnded)
*
* @returns {Promise}
* Resolves when the event triggers with the first argument
* to the event handler as the resolution value.
*/
function promiseInstallEvent(addon, event) {
return new Promise(resolve => {
let listener = {};
listener[event] = (install, arg) => {
if (install.addon.id == addon.id) {
AddonManager.removeInstallListener(listener);
resolve(arg);
}
};
AddonManager.addInstallListener(listener);
});
}
/**
* Install an (xpi packaged) extension
*
* @param {string} url
* URL of the .xpi file to install
*
* @returns {Promise}
* Resolves when the extension has been installed with the Addon
* object as the resolution value.
*/
function promiseInstallAddon(url) {
return AddonManager.getInstallForURL(url, null, "application/x-xpinstall")
.then(install => {
ok(install, "Created install");
return new Promise(resolve => {
install.addListener({
onInstallEnded(_install, addon) {
resolve(addon);
},
});
install.install();
});
});
}
function isDefaultIcon(icon) {
// These are basically the same icon, but code within webextensions
// generates references to the former and generic add-ons manager code
// generates referces to the latter.
return (icon == "chrome://browser/content/extension.svg" ||
icon == "chrome://mozapps/skin/extensions/extensionGeneric.svg");
}
function is_hidden(element) {
var style = element.ownerGlobal.getComputedStyle(element);
if (style.display == "none")
return true;
if (style.visibility != "visible")
return true;
if (style.display == "-moz-popup")
return ["hiding", "closed"].indexOf(element.state) != -1;
// Hiding a parent element will hide all its children
if (element.parentNode != element.ownerDocument)
return is_hidden(element.parentNode);
return false;
}
function is_visible(element) {
var style = element.ownerGlobal.getComputedStyle(element);
if (style.display == "none")
return false;
if (style.visibility != "visible")
return false;
if (style.display == "-moz-popup" && element.state != "open")
return false;
// Hiding a parent element will hide all its children
if (element.parentNode != element.ownerDocument)
return is_visible(element.parentNode);
return true;
}
/**
* Check the contents of an individual permission string.
* This function is fairly specific to the use here and probably not
* suitable for re-use elsewhere...
*
* @param {string} string
* The string value to check (i.e., pulled from the DOM)
* @param {string} key
* The key in browser.properties for the localized string to
* compare with.
* @param {string|null} param
* Optional string to substitute for %S in the localized string.
* @param {string} msg
* The message to be emitted as part of the actual test.
*/
function checkPermissionString(string, key, param, msg) {
let localizedString = param ?
gBrowserBundle.formatStringFromName(key, [param], 1) :
gBrowserBundle.GetStringFromName(key);
// If this is a parameterized string and the parameter isn't given,
// just do a simple comparison of the text before and after the %S
if (localizedString.includes("%S")) {
let i = localizedString.indexOf("%S");
ok(string.startsWith(localizedString.slice(0, i)), msg);
ok(string.endsWith(localizedString.slice(i + 2)), msg);
} else {
is(string, localizedString, msg);
}
}
/**
* Check the contents of a permission popup notification
*
* @param {Window} panel
* The popup window.
* @param {string|regexp|function} checkIcon
* The icon expected to appear in the notification. If this is a
* string, it must match the icon url exactly. If it is a
* regular expression it is tested against the icon url, and if
* it is a function, it is called with the icon url and returns
* true if the url is correct.
* @param {array} permissions
* The expected entries in the permissions list. Each element
* in this array is itself a 2-element array with the string key
* for the item (e.g., "webextPerms.description.foo") and an
* optional formatting parameter.
*/
function checkNotification(panel, checkIcon, permissions) {
let icon = panel.getAttribute("icon");
let ul = document.getElementById("addon-webext-perm-list");
let header = document.getElementById("addon-webext-perm-intro");
if (checkIcon instanceof RegExp) {
ok(checkIcon.test(icon), "Notification icon is correct");
} else if (typeof checkIcon == "function") {
ok(checkIcon(icon), "Notification icon is correct");
} else {
is(icon, checkIcon, "Notification icon is correct");
}
is(ul.childElementCount, permissions.length, `Permissions list has ${permissions.length} entries`);
if (permissions.length == 0) {
is(header.getAttribute("hidden"), "true", "Permissions header is hidden");
} else {
is(header.getAttribute("hidden"), "", "Permissions header is visible");
}
for (let i in permissions) {
let [key, param] = permissions[i];
checkPermissionString(ul.children[i].textContent, key, param,
`Permission number ${i + 1} is correct`);
}
}
/**
* Test that install-time permission prompts work for a given
* installation method.
*
* @param {Function} installFn
* Callable that takes the name of an xpi file to install and
* starts to install it. Should return a Promise that resolves
* when the install is finished or rejects if the install is canceled.
* @param {string} telemetryBase
* If supplied, the base type for telemetry events that should be
* recorded for this install method.
*
* @returns {Promise}
*/
async function testInstallMethod(installFn, telemetryBase) {
const PERMS_XPI = "browser_webext_permissions.xpi";
const NO_PERMS_XPI = "browser_webext_nopermissions.xpi";
const ID = "permissions@test.mozilla.org";
await SpecialPowers.pushPrefEnv({set: [
["extensions.webapi.testing", true],
["extensions.install.requireBuiltInCerts", false],
]});
if (telemetryBase !== undefined) {
hookExtensionsTelemetry();
}
let testURI = makeURI("https://example.com/");
Services.perms.add(testURI, "install", Services.perms.ALLOW_ACTION);
registerCleanupFunction(() => Services.perms.remove(testURI, "install"));
async function runOnce(filename, cancel) {
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
let installPromise = new Promise(resolve => {
let listener = {
onDownloadCancelled() {
AddonManager.removeInstallListener(listener);
resolve(false);
},
onDownloadFailed() {
AddonManager.removeInstallListener(listener);
resolve(false);
},
onInstallCancelled() {
AddonManager.removeInstallListener(listener);
resolve(false);
},
onInstallEnded() {
AddonManager.removeInstallListener(listener);
resolve(true);
},
onInstallFailed() {
AddonManager.removeInstallListener(listener);
resolve(false);
},
};
AddonManager.addInstallListener(listener);
});
let installMethodPromise = installFn(filename);
let panel = await promisePopupNotificationShown("addon-webext-permissions");
if (filename == PERMS_XPI) {
// The icon should come from the extension, don't bother with the precise
// path, just make sure we've got a jar url pointing to the right path
// inside the jar.
checkNotification(panel, /^jar:file:\/\/.*\/icon\.png$/, [
["webextPerms.hostDescription.wildcard", "wildcard.domain"],
["webextPerms.hostDescription.oneSite", "singlehost.domain"],
["webextPerms.description.nativeMessaging"],
["webextPerms.description.tabs"],
["webextPerms.description.history"],
]);
} else if (filename == NO_PERMS_XPI) {
checkNotification(panel, isDefaultIcon, []);
}
if (cancel) {
panel.secondaryButton.click();
try {
await installMethodPromise;
} catch (err) {}
} else {
// Look for post-install notification
let postInstallPromise = promisePopupNotificationShown("addon-installed");
panel.button.click();
// Press OK on the post-install notification
panel = await postInstallPromise;
panel.button.click();
await installMethodPromise;
}
let result = await installPromise;
let addon = await AddonManager.getAddonByID(ID);
if (cancel) {
ok(!result, "Installation was cancelled");
is(addon, null, "Extension is not installed");
} else {
ok(result, "Installation completed");
isnot(addon, null, "Extension is installed");
addon.uninstall();
}
await BrowserTestUtils.removeTab(tab);
}
// A few different tests for each installation method:
// 1. Start installation of an extension that requests no permissions,
// verify the notification contents, then cancel the install
await runOnce(NO_PERMS_XPI, true);
// 2. Same as #1 but with an extension that requests some permissions.
await runOnce(PERMS_XPI, true);
// 3. Repeat with the same extension from step 2 but this time,
// accept the permissions to install the extension. (Then uninstall
// the extension to clean up.)
await runOnce(PERMS_XPI, false);
if (telemetryBase !== undefined) {
// Should see 2 canceled installs followed by 1 successful install
// for this method.
expectTelemetry([`${telemetryBase}Rejected`, `${telemetryBase}Rejected`, `${telemetryBase}Accepted`]);
}
await SpecialPowers.popPrefEnv();
}
// The tests in this directory install a bunch of extensions but they
// need to uninstall them before exiting, as a stray leftover extension
// after one test can foul up subsequent tests.
// So, add a task to run before any tests that grabs a list of all the
// add-ons that are pre-installed in the test environment and then checks
// the list of installed add-ons at the end of the test to make sure no
// new add-ons have been added.
// Individual tests can store a cleanup function in the testCleanup global
// to ensure it gets called before the final check is performed.
let testCleanup;
add_task(async function() {
let addons = await AddonManager.getAllAddons();
let existingAddons = new Set(addons.map(a => a.id));
registerCleanupFunction(async function() {
if (testCleanup) {
await testCleanup();
testCleanup = null;
}
for (let addon of await AddonManager.getAllAddons()) {
if (!existingAddons.has(addon.id)) {
ok(false, `Addon ${addon.id} was left installed at the end of the test`);
addon.uninstall();
}
}
});
});
let collectedTelemetry = [];
function hookExtensionsTelemetry() {
let originalHistogram = ExtensionsUI.histogram;
ExtensionsUI.histogram = {
add(value) { collectedTelemetry.push(value); },
};
registerCleanupFunction(() => {
is(collectedTelemetry.length, 0, "No unexamined telemetry after test is finished");
ExtensionsUI.histogram = originalHistogram;
});
}
function expectTelemetry(values) {
Assert.deepEqual(values, collectedTelemetry);
collectedTelemetry = [];
}