fune/toolkit/mozapps/extensions/test/browser/browser_html_detail_permissions.js

619 lines
19 KiB
JavaScript

/* eslint max-len: ["error", 80] */
const { AddonTestUtils } = ChromeUtils.import(
"resource://testing-common/AddonTestUtils.jsm"
);
const { ExtensionPermissions } = ChromeUtils.import(
"resource://gre/modules/ExtensionPermissions.jsm"
);
AddonTestUtils.initMochitest(this);
async function background() {
browser.permissions.onAdded.addListener(perms => {
browser.test.sendMessage("permission-added", perms);
});
browser.permissions.onRemoved.addListener(perms => {
browser.test.sendMessage("permission-removed", perms);
});
}
async function getExtensions({ manifest_version = 2 } = {}) {
let extensions = {
"addon0@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 0",
applications: { gecko: { id: "addon0@mochi.test" } },
permissions: ["alarms", "contextMenus"],
},
background,
useAddonManager: "temporary",
}),
"addon1@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 1",
applications: { gecko: { id: "addon1@mochi.test" } },
permissions: ["alarms", "contextMenus", "tabs", "webNavigation"],
// Note: for easier testing, we merge host_permissions into permissions
// when loading mv2 extensions, see ExtensionTestCommon.generateFiles.
host_permissions: ["<all_urls>", "file://*/*"],
},
background,
useAddonManager: "temporary",
}),
"addon2@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 2",
applications: { gecko: { id: "addon2@mochi.test" } },
permissions: ["alarms", "contextMenus"],
optional_permissions: ["http://mochi.test/*"],
},
background,
useAddonManager: "temporary",
}),
"addon3@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 3",
version: "1.0",
applications: { gecko: { id: "addon3@mochi.test" } },
permissions: ["tabs"],
optional_permissions: ["webNavigation", "<all_urls>"],
},
background,
useAddonManager: "temporary",
}),
"addon4@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 4",
applications: { gecko: { id: "addon4@mochi.test" } },
optional_permissions: ["tabs", "webNavigation"],
},
background,
useAddonManager: "temporary",
}),
"addon5@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 5",
applications: { gecko: { id: "addon5@mochi.test" } },
optional_permissions: ["*://*/*"],
},
background,
useAddonManager: "temporary",
}),
"priv6@mochi.test": ExtensionTestUtils.loadExtension({
isPrivileged: true,
manifest: {
manifest_version,
name: "Privileged add-on 6",
applications: { gecko: { id: "priv6@mochi.test" } },
optional_permissions: [
"file://*/*",
"about:reader*",
"resource://pdf.js/*",
"*://*.mozilla.com/*",
"*://*/*",
"<all_urls>",
],
},
background,
useAddonManager: "temporary",
}),
"addon7@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 7",
applications: { gecko: { id: "addon7@mochi.test" } },
optional_permissions: ["<all_urls>", "*://*/*", "file://*/*"],
},
background,
useAddonManager: "temporary",
}),
"addon8@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 8",
applications: { gecko: { id: "addon8@mochi.test" } },
optional_permissions: ["*://*/*", "file://*/*", "<all_urls>"],
},
background,
useAddonManager: "temporary",
}),
"other@mochi.test": ExtensionTestUtils.loadExtension({
manifest: {
manifest_version,
name: "Test add-on 6",
applications: { gecko: { id: "other@mochi.test" } },
optional_permissions: [
"tabs",
"webNavigation",
"<all_urls>",
"*://*/*",
],
},
useAddonManager: "temporary",
}),
};
for (let ext of Object.values(extensions)) {
await ext.startup();
}
return extensions;
}
async function runTest(options) {
let {
extension,
addonId,
permissions = [],
optional_permissions = [],
optional_enabled = [],
// Map<permission->string> to check optional_permissions against, if set.
optional_strings = {},
view,
} = options;
if (extension) {
addonId = extension.id;
}
let win = view || (await loadInitialView("extension"));
let card = getAddonCard(win, addonId);
let permsSection = card.querySelector("addon-permissions-list");
if (!permsSection) {
ok(!card.hasAttribute("expanded"), "The list card is not expanded");
let loaded = waitForViewLoad(win);
card.querySelector('[action="expand"]').click();
await loaded;
}
card = getAddonCard(win, addonId);
let { deck, tabGroup } = card.details;
let permsBtn = tabGroup.querySelector('[name="permissions"]');
let permsShown = BrowserTestUtils.waitForEvent(deck, "view-changed");
permsBtn.click();
await permsShown;
permsSection = card.querySelector("addon-permissions-list");
let rows = Array.from(permsSection.querySelectorAll(".addon-detail-row"));
let permission_rows = Array.from(
permsSection.querySelectorAll(".permission-info")
);
// Last row is the learn more link.
info("Check learn more link");
let link = rows[rows.length - 1].firstElementChild;
let rootUrl = Services.urlFormatter.formatURLPref("app.support.baseURL");
let url = rootUrl + "extension-permissions";
is(link.href, url, "The URL is set");
is(link.getAttribute("target"), "_blank", "The link opens in a new tab");
// We should have one more row (learn more) that the combined permissions,
// or if no permissions, 2 rows.
let num_permissions = permissions.length + optional_permissions.length;
is(
permission_rows.length,
num_permissions,
"correct number of details rows are present"
);
info("Check displayed permissions");
if (!num_permissions) {
is(
win.document.l10n.getAttributes(rows[0]).id,
"addon-permissions-empty",
"There's a message when no permissions are shown"
);
}
if (permissions.length) {
for (let name of permissions) {
// Check the permission-info class to make sure it's for a permission.
let row = permission_rows.shift();
ok(
row.classList.contains("permission-info"),
`required permission row for ${name}`
);
}
}
let addon = await AddonManager.getAddonByID(addonId);
info(`addon ${addon.id} is ${addon.userDisabled ? "disabled" : "enabled"}`);
function waitForPermissionChange(id) {
return new Promise(resolve => {
info(`listening for change on ${id}`);
let listener = (type, data) => {
info(`change permissions ${JSON.stringify(data)}`);
if (data.extensionId !== id) {
return;
}
ExtensionPermissions.removeListener(listener);
resolve(data);
};
ExtensionPermissions.addListener(listener);
});
}
// This tests the permission change and button state when the user
// changes the state in about:addons.
async function testTogglePermissionButton(
permission,
button,
excpectDisabled = false
) {
let enabled = optional_enabled.includes(permission);
if (excpectDisabled) {
enabled = !enabled;
}
is(
button.checked,
enabled,
`permission is set correctly for ${permission}: ${button.checked}`
);
let change;
if (addon.userDisabled || !extension) {
change = waitForPermissionChange(addonId);
} else if (!enabled) {
change = extension.awaitMessage("permission-added");
} else {
change = extension.awaitMessage("permission-removed");
}
button.click();
let perms = await change;
if (addon.userDisabled || !extension) {
perms = enabled ? perms.removed : perms.added;
}
ok(
perms.permissions.includes(permission) ||
perms.origins.includes(permission),
"permission was toggled"
);
await BrowserTestUtils.waitForCondition(async () => {
return button.checked == !enabled;
}, "button changed state");
}
// This tests that the button changes state if the permission is
// changed outside of about:addons
async function testExternalPermissionChange(permission, button) {
let enabled = button.checked;
let type = button.getAttribute("permission-type");
let change;
if (addon.userDisabled || !extension) {
change = waitForPermissionChange(addonId);
} else if (!enabled) {
change = extension.awaitMessage("permission-added");
} else {
change = extension.awaitMessage("permission-removed");
}
let permissions = { permissions: [], origins: [] };
if (type == "origin") {
permissions.origins = [permission];
} else {
permissions.permissions = [permission];
}
if (enabled) {
await ExtensionPermissions.remove(addonId, permissions);
} else {
await ExtensionPermissions.add(addonId, permissions);
}
let perms = await change;
if (addon.userDisabled || !extension) {
perms = enabled ? perms.removed : perms.added;
}
ok(
perms.permissions.includes(permission) ||
perms.origins.includes(permission),
"permission was toggled"
);
await BrowserTestUtils.waitForCondition(async () => {
return button.checked == !enabled;
}, "button changed state");
}
// This tests that changing the permission on another addon does
// not change the UI for the addon we're testing.
async function testOtherPermissionChange(permission, toggle) {
let type = toggle.getAttribute("permission-type");
let otherId = "other@mochi.test";
let change = waitForPermissionChange(otherId);
let perms = await ExtensionPermissions.get(otherId);
let existing = type == "origin" ? perms.origins : perms.permissions;
let permissions = { permissions: [], origins: [] };
if (type == "origin") {
permissions.origins = [permission];
} else {
permissions.permissions = [permission];
}
if (existing.includes(permission)) {
await ExtensionPermissions.remove(otherId, permissions);
} else {
await ExtensionPermissions.add(otherId, permissions);
}
await change;
}
if (optional_permissions.length) {
for (let name of optional_permissions) {
// Check the row is a permission row with the correct key on the toggle
// control.
let row = permission_rows.shift();
let label = row.firstElementChild;
let str = optional_strings[name];
if (str) {
is(label.textContent, str, `Expected permission string ${str}`);
}
let toggle = label.lastElementChild;
ok(
row.classList.contains("permission-info"),
`optional permission row for ${name}`
);
is(
toggle.getAttribute("permission-key"),
name,
`optional permission toggle exists for ${name}`
);
await testTogglePermissionButton(name, toggle);
await testTogglePermissionButton(name, toggle, true);
// make a change "outside" the UI and check the values.
// toggle twice to test both add/remove.
await testExternalPermissionChange(name, toggle);
// change another addon to mess around with optional permission
// values to see if it effects the addon we're testing here. The
// next check would fail if anythign bleeds onto other addons.
await testOtherPermissionChange(name, toggle);
// repeate the "outside" test.
await testExternalPermissionChange(name, toggle);
}
}
if (!view) {
await closeView(win);
}
}
async function testPermissionsView({ manifestV3enabled, manifest_version }) {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", manifestV3enabled]],
});
// pre-set a permission prior to starting extensions.
await ExtensionPermissions.add("addon4@mochi.test", {
permissions: ["tabs"],
origins: [],
});
let extensions = await getExtensions({ manifest_version });
info("Check add-on with required permissions");
if (manifest_version < 3) {
await runTest({
extension: extensions["addon1@mochi.test"],
permissions: ["<all_urls>", "tabs", "webNavigation"],
});
} else {
await runTest({
extension: extensions["addon1@mochi.test"],
permissions: ["tabs", "webNavigation"],
optional_permissions: ["<all_urls>"],
});
}
info("Check add-on without any displayable permissions");
await runTest({ extension: extensions["addon0@mochi.test"] });
info("Check add-on with only one optional origin.");
await runTest({
extension: extensions["addon2@mochi.test"],
optional_permissions: manifestV3enabled ? ["http://mochi.test/*"] : [],
optional_strings: {
"http://mochi.test/*": "Access your data for http://mochi.test",
},
});
info("Check add-on with both required and optional permissions");
await runTest({
extension: extensions["addon3@mochi.test"],
permissions: ["tabs"],
optional_permissions: ["webNavigation", "<all_urls>"],
});
// Grant a specific optional host permission not listed in the manifest.
await ExtensionPermissions.add("addon3@mochi.test", {
permissions: [],
origins: ["https://example.com/*"],
});
extensions["addon3@mochi.test"].awaitMessage("permission-added");
info("Check addon3 again and expect the new optional host permission");
await runTest({
extension: extensions["addon3@mochi.test"],
permissions: ["tabs"],
optional_permissions: [
"webNavigation",
"<all_urls>",
...(manifestV3enabled ? ["https://example.com/*"] : []),
],
optional_enabled: ["https://example.com/*"],
optional_strings: {
"https://example.com/*": "Access your data for https://example.com",
},
});
info("Check add-on with only optional permissions, tabs is pre-enabled");
await runTest({
extension: extensions["addon4@mochi.test"],
optional_permissions: ["tabs", "webNavigation"],
optional_enabled: ["tabs"],
});
info("Check add-on with a global match pattern in place of all urls");
await runTest({
extension: extensions["addon5@mochi.test"],
optional_permissions: ["*://*/*"],
});
info("Check privileged add-on with non-web origin permissions");
await runTest({
extension: extensions["priv6@mochi.test"],
optional_permissions: [
"<all_urls>",
...(manifestV3enabled ? ["*://*.mozilla.com/*"] : []),
],
optional_strings: {
"*://*.mozilla.com/*":
"Access your data for sites in the *://mozilla.com domain",
},
});
info(`Check that <all_urls> is used over other "all websites" permissions`);
await runTest({
extension: extensions["addon7@mochi.test"],
optional_permissions: ["<all_urls>"],
});
info(`And again with permissions in the opposite order in the manifest`);
await runTest({
extension: extensions["addon8@mochi.test"],
optional_permissions: ["<all_urls>"],
});
for (let ext of Object.values(extensions)) {
await ext.unload();
}
await SpecialPowers.popPrefEnv();
}
add_task(async function testPermissionsView_MV2_manifestV3disabled() {
await testPermissionsView({ manifestV3enabled: false, manifest_version: 2 });
});
add_task(async function testPermissionsView_MV2_manifestV3enabled() {
await testPermissionsView({ manifestV3enabled: true, manifest_version: 2 });
});
add_task(async function testPermissionsView_MV3() {
await testPermissionsView({ manifestV3enabled: true, manifest_version: 3 });
});
add_task(async function testPermissionsViewStates() {
let ID = "addon@mochi.test";
let extension = ExtensionTestUtils.loadExtension({
manifest: {
name: "Test add-on 3",
version: "1.0",
applications: { gecko: { id: ID } },
permissions: ["tabs"],
optional_permissions: ["webNavigation", "<all_urls>"],
},
useAddonManager: "permanent",
});
await extension.startup();
info(
"Check toggling permissions on a disabled addon with addon3@mochi.test."
);
let view = await loadInitialView("extension");
let addon = await AddonManager.getAddonByID(ID);
await addon.disable();
ok(addon.userDisabled, "addon is disabled");
await runTest({
extension,
permissions: ["tabs"],
optional_permissions: ["webNavigation", "<all_urls>"],
view,
});
await addon.enable();
ok(!addon.userDisabled, "addon is enabled");
async function install_addon(extensionData) {
let xpi = await AddonTestUtils.createTempWebExtensionFile(extensionData);
let { addon } = await AddonTestUtils.promiseInstallFile(xpi);
return addon;
}
function wait_for_addon_item_updated(addonId) {
return BrowserTestUtils.waitForEvent(getAddonCard(view, addonId), "update");
}
let promiseItemUpdated = wait_for_addon_item_updated(ID);
addon = await install_addon({
manifest: {
name: "Test add-on 3",
version: "2.0",
applications: { gecko: { id: ID } },
optional_permissions: ["webNavigation"],
},
useAddonManager: "permanent",
});
is(addon.version, "2.0", "addon upgraded");
await promiseItemUpdated;
await runTest({
addonId: addon.id,
optional_permissions: ["webNavigation"],
view,
});
// While the view is still available, test setting a permission
// that is not in the manifest of the addon.
let card = getAddonCard(view, addon.id);
await Assert.rejects(
card.setAddonPermission("webRequest", "permission", "add"),
/permission missing from manifest/,
"unable to set the addon permission"
);
await closeView(view);
await extension.unload();
});
add_task(async function testAllUrlsNotGrantedUnconditionally_MV3() {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", true]],
});
const extension = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 3,
host_permissions: ["<all_urls>"],
},
async background() {
const perms = await browser.permissions.getAll();
browser.test.sendMessage("granted-permissions", perms);
},
});
await extension.startup();
const perms = await extension.awaitMessage("granted-permissions");
ok(
!perms.origins.includes("<all_urls>"),
"Optional <all_urls> should not be granted as host permission yet"
);
ok(
!perms.permissions.includes("<all_urls>"),
"Optional <all_urls> should not be granted as an API permission neither"
);
await extension.unload();
await SpecialPowers.popPrefEnv();
});