Bug 1911163 - Add full domains list in addon-webext-permissions-notification permissions list. r=desktop-theme-reviewers,fluent-reviewers,willdurand,bolsson,sfoster

Differential Revision: https://phabricator.services.mozilla.com/D218593
This commit is contained in:
Luca Greco 2024-10-03 16:36:18 +00:00
parent 9626bd1576
commit 92862ecb60
7 changed files with 825 additions and 8 deletions

View File

@ -134,6 +134,26 @@ customElements.define(
);
}
get domainsSet() {
if (!this.notification?.options?.customElementOptions) {
return undefined;
}
const { strings } = this.notification.options.customElementOptions;
return strings.fullDomainsList?.domainsSet;
}
get hasFullDomainsList() {
return this.domainsSet?.size;
}
#isFullDomainsListEntryIndex(idx) {
if (!this.hasFullDomainsList) {
return false;
}
const { strings } = this.notification.options.customElementOptions;
return strings.fullDomainsList.msgIdIndex === idx;
}
render() {
const { strings, showIncognitoCheckbox } =
this.notification.options.customElementOptions;
@ -171,10 +191,17 @@ customElements.define(
// (and one for the private browsing checkbox, if it should
// be shown) and return earlier.
if (this.hasMultiplePermissionsEntries) {
for (let msg of strings.msgs) {
for (let [idx, msg] of strings.msgs.entries()) {
let item = doc.createElementNS(HTML_NS, "li");
item.classList.add("webext-perm-granted");
item.textContent = msg;
if (
this.hasFullDomainsList &&
this.#isFullDomainsListEntryIndex(idx)
) {
item.append(this.#createFullDomainsListFragment(msg));
} else {
item.textContent = msg;
}
permsListEl.appendChild(item);
}
if (showIncognitoCheckbox) {
@ -203,10 +230,41 @@ customElements.define(
return;
}
permsSingleEl.textContent = strings.msgs[0];
const msg = strings.msgs[0];
if (this.hasFullDomainsList && this.#isFullDomainsListEntryIndex(0)) {
permsSingleEl.append(this.#createFullDomainsListFragment(msg));
} else {
permsSingleEl.textContent = msg;
}
permsSingleEl.hidden = false;
}
#createFullDomainsListFragment(msg) {
const HTML_NS = "http://www.w3.org/1999/xhtml";
const doc = this.ownerDocument;
const label = doc.createXULElement("label");
label.value = msg;
const domainsList = doc.createElementNS(HTML_NS, "ul");
domainsList.classList.add("webext-perm-domains-list");
// Enforce max-height and ensure the domains list is
// scrollable when there are more than 5 domains.
if (this.domainsSet.size > 5) {
domainsList.classList.add("scrollable-domains-list");
}
for (const domain of this.domainsSet) {
let domainItem = doc.createElementNS(HTML_NS, "li");
domainItem.textContent = domain;
domainsList.appendChild(domainItem);
}
const { DocumentFragment } = this.ownerGlobal;
const fragment = new DocumentFragment();
fragment.append(label);
fragment.append(domainsList);
return fragment;
}
#clearChildElements() {
const { textEl, introEl, permsSingleEl, permsListEl } = this;

View File

@ -1,5 +1,9 @@
"use strict";
ChromeUtils.defineESModuleGetters(this, {
PERMISSION_L10N: "resource://gre/modules/ExtensionPermissionMessages.sys.mjs",
});
// This test case verifies that `permissions.request()` resolves in the
// expected order.
add_task(async function test_permissions_prompt() {
@ -119,3 +123,202 @@ add_task(async function test_permissions_prompt() {
// The extension tabs are automatically closed upon unload.
await extension.unload();
});
// NOTE: more tests covering the full domains list are part of the separate
// test case covering the full domains list when this dialog is being used
// as the addon install prompt (the test case part of the AOM mochitests
// and named testInstallDialogShowsFullDomainsList).
add_task(async function testOptionalPermissionsDialogShowsFullDomainsList() {
await SpecialPowers.pushPrefEnv({
set: [
// These are both expected to be the default, but we are setting
// them explicitly to make sure this test task is always running
// with the prefs set with these values even if we would be
// rolling back the pref value temporarily.
["extensions.ui.installDialogFullDomains", true],
],
});
// Sanity check.
ok(
ExtensionsUI.SHOW_FULL_DOMAINS_LIST,
"Expect SHOW_FULL_DOMAINS_LIST to be enabled"
);
const createTestExtension = ({
id,
domainsListLength = 0,
optional_permissions = [],
}) =>
ExtensionTestUtils.loadExtension({
manifest: {
// Set the generated id as a name to make it easier to recognize the test case
// from dialog screenshots (e.g. in the screenshot captured when the test hits
// a failure).
name: id,
version: "1.0",
browser_specific_settings: {
gecko: { id },
},
optional_permissions: optional_permissions.concat(
new Array(domainsListLength).fill("examplehost").map((v, i) => {
return `*://${v}${i}.com/*`;
})
),
},
files: {
"extpage.html": `<!DOCTYPE html><script src="extpage.js"></script>`,
"extpage.js"() {
browser.test.onMessage.addListener(async msg => {
if (msg !== "optional-origins:request") {
browser.test.fail(`Got unexpected test message ${msg}`);
return;
}
const { optional_permissions } = browser.runtime.getManifest();
const permissions = optional_permissions.filter(
p => !p.startsWith("*://")
);
const origins = optional_permissions.filter(p =>
p.startsWith("*://")
);
browser.test.withHandlingUserInput(() => {
browser.permissions.request({
permissions,
origins,
});
browser.test.sendMessage("optional-origins:requested");
});
});
browser.test.sendMessage("extpage:loaded");
},
},
});
const assertNoDomainsList = popupContentEl => {
const domainsListEl = popupContentEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(!domainsListEl, "Expect no domains list element to be found");
};
const assertOneDomainPermission = hostPermStringEl => {
Assert.equal(
hostPermStringEl.textContent,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-one-domain",
{
domain: "examplehost0.com",
}
),
"Got the expected host permission string on extension with only one granted domain"
);
};
const assertMultipleDomainsPermission = (
domainsListEl,
domainsListLength
) => {
// The permission string associated to XUL label element can be reached as labelEl.value.
Assert.equal(
domainsListEl.previousElementSibling.value,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-multiple-domains",
{
domainCount: domainsListLength,
}
),
`Got the expected host permission string on extension with ${this.domainsListLength} granted domain`
);
Assert.deepEqual(
Array.from(domainsListEl.querySelectorAll("li")).map(
el => el.textContent
),
new Array(domainsListLength)
.fill("examplehost")
.map((v, i) => `${v}${i}.com`),
"Got the expected domains listed in the domains list element"
);
};
const TEST_CASES = [
{
msg: "Test request API permission and no origins",
id: "api-and-no-domains@test-ext",
optional_permissions: ["history"],
domainsListLength: 0,
verifyDialog(popupContentEl) {
assertNoDomainsList(popupContentEl);
},
},
{
msg: "Test request access to a single domain",
id: "single-domain@test-ext",
optional_permissions: [],
domainsListLength: 1,
verifyDialog(popupContentEl) {
assertNoDomainsList(popupContentEl);
assertOneDomainPermission(popupContentEl.permsSingleEl);
},
},
{
msg: "Test request API permission and access to a single domain",
id: "api-and-single-domain@test-ext",
optional_permissions: ["history"],
domainsListLength: 1,
verifyDialog(popupContentEl) {
assertNoDomainsList(popupContentEl);
assertOneDomainPermission(
popupContentEl.permsListEl.querySelector("li:first-child")
);
},
},
{
msg: "Test request access to multiple domains",
id: "multiple-domains@test-ext",
optional_permissions: [],
domainsListLength: 10,
verifyDialog(popupContentEl) {
const domainsListEl = popupContentEl.permsSingleEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(domainsListEl, "Expect domains list element to be found");
assertMultipleDomainsPermission(domainsListEl, this.domainsListLength);
},
},
{
msg: "Test request API permision and access to multiple domains",
id: "api-and-multiple-domains@test-ext",
optional_permissions: ["history"],
domainsListLength: 10,
verifyDialog(popupContentEl) {
const domainsListEl = popupContentEl.permsListEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(domainsListEl, "Expect domains list element to be found");
assertMultipleDomainsPermission(domainsListEl, this.domainsListLength);
},
},
];
for (const testCase of TEST_CASES) {
info(testCase.msg);
const extension = createTestExtension(testCase);
await extension.startup();
let extPageURL = `moz-extension://${extension.uuid}/extpage.html`;
await BrowserTestUtils.withNewTab(extPageURL, async () => {
let promiseRequestDisalog = promisePopupNotificationShown(
"addon-webext-permissions"
);
await extension.awaitMessage("extpage:loaded");
extension.sendMessage("optional-origins:request");
await extension.awaitMessage("optional-origins:requested");
const popupContentEl = await promiseRequestDisalog;
testCase.verifyDialog(popupContentEl);
});
await extension.unload();
}
});

View File

@ -40,6 +40,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"SHOW_FULL_DOMAINS_LIST",
"extensions.ui.installDialogFullDomains",
true
);
const DEFAULT_EXTENSION_ICON =
"chrome://mozapps/skin/extensions/extensionGeneric.svg";
@ -65,6 +72,10 @@ export var ExtensionsUI = {
pendingNotifications: new WeakMap(),
get SHOW_FULL_DOMAINS_LIST() {
return lazy.SHOW_FULL_DOMAINS_LIST;
},
get POSTINSTALL_PRIVATEBROWSING_CHECKBOX() {
return lazy.POSTINSTALL_PRIVATEBROWSING_CHECKBOX;
},
@ -369,9 +380,12 @@ export var ExtensionsUI = {
// Create a set of formatted strings for a permission prompt
_buildStrings(info) {
const strings = lazy.ExtensionData.formatPermissionStrings(info, {
collapseOrigins: true,
});
const strings = lazy.ExtensionData.formatPermissionStrings(
info,
this.SHOW_FULL_DOMAINS_LIST
? { fullDomainsList: true }
: { collapseOrigins: true }
);
strings.addonName = info.addon.name;
return strings;
},

View File

@ -21,6 +21,12 @@ html|*.addon-webext-perm-list {
> html|li {
list-style: none;
&.webext-perm-granted {
/* NOTE: Insert line breaks on long permission strings (or domain name included
* in the localized string that ends up be overflowing */
overflow-wrap: break-word;
}
/* style the permissions list items that are not editable to use the check.svg image */
&.webext-perm-granted::before {
content: "";
@ -40,8 +46,39 @@ html|*.addon-webext-perm-list {
}
}
html|ul.webext-perm-domains-list {
--domains-list-border-color: var(--border-color-deemphasized);
--domains-list-text-color: var(--text-color-deemphasized);
border: var(--border-width) solid var(--domains-list-border-color);
border-radius: var(--border-radius-small);
margin-block: var(--space-small);
margin-inline: calc(var(--size-item-small) + var(--space-small)) 0;
padding-block: var(--space-xsmall);
padding-inline-start: var(--size-item-medium);
max-width: 80vw;
overflow: auto;
/* max-height is set when the domains list is longer than 5 domains
* to force the domains list to become scrollable. */
&.scrollable-domains-list {
max-height: 5.5lh;
}
> html|li {
list-style: disc;
color: var(--domains-list-text-color);
/* NOTE: Insert line breaks anywhere in long domain names that would be overflowing */
overflow-wrap: anywhere;
}
}
.addon-webext-perm-single-entry {
margin-top: 11px;
/* NOTE: Insert line breaks on long permission strings (or domain name included
* in the localized string that ends up be overflowing */
overflow-wrap: break-word;
}
.addon-webext-perm-text,

View File

@ -2448,6 +2448,9 @@ export class ExtensionData {
* @param {boolean} [options.buildOptionalOrigins]
* Wether to build optional origins Maps for permission
* controls. Defaults to false.
* @param {boolean} [options.fullDomainsList]
* Wether to include the full domains set in the returned
* results. Defaults to false.
*
* @returns {object} An object with properties containing localized strings
* for various elements of a permission dialog. The "header"
@ -2463,6 +2466,14 @@ export class ExtensionData {
* "object.optionalOrigins" is a map of a host permission to localized strings
* describing the host permission, where appropriate. Currently only
* all url style permissions are included.
*
* "object.fullDomainsList" is an object with a Set of the
* full domains list (with the property name "domainsSet")
* and the index of the corresponding message string (with
* the property name "msgIdIndex"). This property is
* expected to be set only if "options.fullDomainsList" is
* passed as true and the extension doesn't include
* allUrls origin permissions.
*/
static formatPermissionStrings(
{
@ -2474,7 +2485,11 @@ export class ExtensionData {
type,
unsigned,
},
{ collapseOrigins = false, buildOptionalOrigins = false } = {}
{
collapseOrigins = false,
buildOptionalOrigins = false,
fullDomainsList = false,
} = {}
) {
const l10n = lazy.PERMISSION_L10N;
@ -2596,7 +2611,7 @@ export class ExtensionData {
// first, then individual host permissions.
if (allUrls) {
msgIds.push("webext-perms-host-description-all-urls");
} else {
} else if (!fullDomainsList) {
// Formats a list of host permissions. If we have 4 or fewer, display
// them all, otherwise display the first 3 followed by an item that
// says "...plus N others"
@ -2628,6 +2643,29 @@ export class ExtensionData {
);
}
if (!allUrls && fullDomainsList) {
const allHostPermissions = wildcards.union(sites);
if (allHostPermissions.size > 1) {
msgIds.push({
id: "webext-perms-host-description-multiple-domains",
args: {
domainCount: allHostPermissions.size,
},
});
result.fullDomainsList = {
domainsSet: allHostPermissions,
msgIdIndex: msgIds.length - 1,
};
} else if (allHostPermissions.size) {
msgIds.push({
id: "webext-perms-host-description-one-domain",
args: {
domain: Array.from(allHostPermissions)[0],
},
});
}
}
// Finally, show remaining permissions, in the same order as AMO.
// The permissions are sorted alphabetically by the permission
// string to match AMO.

View File

@ -76,6 +76,22 @@ webext-perms-host-description-too-many-sites =
*[other] Access your data on { $domainCount } other sites
}
# Variables:
# $domain (String): will be replaced by the DNS host name for which a webextension is requesting access (e.g., mozilla.org),
# $domain should be treated as plural (because it may also include all subdomains, e.g www.mozilla.org, ftp.mozilla.org).
webext-perms-host-description-one-domain = Access your data for sites in { $domain } domains
# Permission string used for webextensions requesting access to 2 or more domains (and so $domainCount is expected to always
# be >= 2, for webextensions requesting access to only one domain the `webext-perms-host-description-one-domain` string is
# used instead).
# Variables:
# $domainCount (Number): Integer indicating the number of websites domains for which this webextension is requesting permission
# (the list of domains will follow this string).
webext-perms-host-description-multiple-domains =
{ $domainCount ->
*[other] Access your data for sites in { $domainCount } domains
}
## Headers used in the webextension permissions dialog for synthetic add-ons.
## The part of the string describing what privileges the extension gives should be consistent
## with the value of webext-site-perms-description-gated-perms-{sitePermission}.

View File

@ -12,6 +12,10 @@ const ADDON_ID = "addon1@test.mozilla.org";
const CUSTOM_THEME_ID = "theme1@test.mozilla.org";
const DEFAULT_THEME_ID = "default-theme@mozilla.org";
ChromeUtils.defineESModuleGetters(this, {
PERMISSION_L10N: "resource://gre/modules/ExtensionPermissionMessages.sys.mjs",
});
AddonTestUtils.initMochitest(this);
function assertDisabledSideloadedExtensionElement(managerWindow, addonElement) {
@ -176,3 +180,450 @@ add_task(async function test_sideloaded_extension_permissions_prompt() {
await close_manager(manager);
await addon.uninstall();
});
add_task(async function testInstallDialogShowsFullDomainsList() {
await SpecialPowers.pushPrefEnv({
set: [
// These are both expected to be the default, but we are setting
// them explicitly to make sure this test task is always running
// with the prefs set with these values even if we would be
// rolling back the pref value temporarily.
["extensions.ui.installDialogFullDomains", true],
["extensions.ui.postInstallPrivateBrowsingCheckbox", false],
],
});
// Sanity check.
ok(
ExtensionsUI.SHOW_FULL_DOMAINS_LIST,
"Expect SHOW_FULL_DOMAINS_LIST to be enabled"
);
ok(
!ExtensionsUI.POSTINSTALL_PRIVATEBROWSING_CHECKBOX,
"Expect POSTINSTALL_PRIVATEBROWSING_CHECKBOX to be disabled"
);
const createTestExtensionXPI = ({
id,
domainsListLength = 0,
permissions = [],
incognito = "spanning",
}) =>
AddonTestUtils.createTempWebExtensionFile({
manifest: {
// Set the generated id as a name to make it easier to recognize the test case
// from dialog screenshots (e.g. in the screenshot captured when the test hits
// a failure).
name: id,
version: "1.0",
browser_specific_settings: {
gecko: { id },
},
incognito,
permissions: permissions.concat(
new Array(domainsListLength).fill("examplehost").map((v, i) => {
return `*://${v}${i}.com/*`;
})
),
},
});
const LONG_DOMAIN_NAME = `averylongdomainname.${new Array(40)
.fill("x")
.join("")}.com`;
const assertPermsElVisibility = (popupContentEl, noIncognitoCheckbox) => {
// We expect the host permissions entry to be the only entry to be shown
// if the incognito checkbox isn't expected to be visible for the test
// extension (because the test extension doesn't request any other
// permission and each test case is executed with and without opting-out
// of the private browsing access).
Assert.equal(
BrowserTestUtils.isHidden(popupContentEl.permsListEl),
noIncognitoCheckbox,
`Expect the permissions list element to be ${
noIncognitoCheckbox ? "hidden" : "visible"
}`
);
Assert.equal(
BrowserTestUtils.isVisible(popupContentEl.permsSingleEl),
noIncognitoCheckbox,
`Expect the single permission element to be ${
noIncognitoCheckbox ? "visible" : "hidden"
}`
);
};
const assertNoDomainsList = popupContentEl => {
const domainsListEl = popupContentEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(!domainsListEl, "Expect no domain list element to be found");
};
const TEST_CASES = [
{
msg: "Test install extension with no host permissions",
id: "no-domains",
domainsListLength: 0,
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertNoDomainsList(popupContentEl);
Assert.ok(
BrowserTestUtils.isHidden(popupContentEl.permsListEl),
`Expect the permissions list element to be hidden`
);
Assert.equal(
BrowserTestUtils.isHidden(popupContentEl.permsSingleEl),
noIncognitoCheckbox,
`Expect the permissions list element to be ${
noIncognitoCheckbox ? "hidden" : "visible"
}`
);
},
},
{
msg: "Test install extension with one domain listed in host permissions",
id: "one-domain",
domainsListLength: 1,
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
assertNoDomainsList(popupContentEl);
const hostPermStringEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl
: popupContentEl.permsListEl.querySelector("li.webext-perm-granted");
Assert.ok(
hostPermStringEl,
"Expect one granted permission string element"
);
Assert.equal(
hostPermStringEl.textContent,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-one-domain",
{
domain: "examplehost0.com",
}
),
"Got the expected host permission string on extension with only one granted domain"
);
Assert.ok(
BrowserTestUtils.isVisible(hostPermStringEl),
"Expect the host permission string to be visible"
);
},
},
{
msg: "Test install extension with less than 6 domains listed in host permissions",
id: "few-domains",
domainsListLength: 5,
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
const domainsListEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl.querySelector(
".webext-perm-domains-list"
)
: popupContentEl.permsListEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(
domainsListEl,
"Expect domains list element to be found inside the permission list element"
);
Assert.ok(
BrowserTestUtils.isVisible(domainsListEl),
"Expect the domains list element to be visible"
);
Assert.equal(
domainsListEl.scrollTopMax,
0,
"Expect domains list to not be scrollable (chromeOnly scrollTopMax set to 0)"
);
// The permission string associated to XUL label element can be reached as labelEl.value.
Assert.equal(
domainsListEl.previousElementSibling.value,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-multiple-domains",
{
domainCount: this.domainsListLength,
}
),
`Got the expected host permission string on extension with ${this.domainsListLength} granted domain`
);
Assert.deepEqual(
Array.from(domainsListEl.querySelectorAll("li")).map(
el => el.textContent
),
new Array(this.domainsListLength)
.fill("examplehost")
.map((v, i) => `${v}${i}.com`),
"Got the expected domains listed in the domains list element"
);
},
},
{
msg: "Test install extension with many domains listed in host permissions",
id: "many-domains",
domainsListLength: 20,
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
const domainsListEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl.querySelector(
".webext-perm-domains-list"
)
: popupContentEl.permsListEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(
domainsListEl,
"Expect domains list element to be found inside the permission list element"
);
Assert.ok(
BrowserTestUtils.isVisible(domainsListEl),
"Expect the domains list element to be visible"
);
Assert.greater(
domainsListEl.scrollTopMax,
domainsListEl.clientHeight,
"Expect domains list to be scrollable (chromeOnly scrollTopMax greater than clientHeight)"
);
// The permission string associated to XUL label element can be reached as labelEl.value.
Assert.equal(
domainsListEl.previousElementSibling.value,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-multiple-domains",
{
domainCount: this.domainsListLength,
}
),
`Got the expected host permission string on extension with ${this.domainsListLength} granted domain`
);
Assert.deepEqual(
Array.from(domainsListEl.querySelectorAll("li")).map(
el => el.textContent
),
new Array(this.domainsListLength)
.fill("examplehost")
.map((v, i) => `${v}${i}.com`),
"Got the expected domains listed in the domains list element"
);
},
},
{
msg: "Test text wrapping on a single long domain name",
id: "one-long-domain",
domainsListLength: 0,
permissions: [`*://${LONG_DOMAIN_NAME}/*`],
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
const hostPermStringEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl
: popupContentEl.permsListEl.querySelector("li.webext-perm-granted");
Assert.equal(
hostPermStringEl.childNodes[0].nodeType,
hostPermStringEl.TEXT_NODE,
"Expect to have host permission element child to be a text node"
);
Assert.equal(
hostPermStringEl.childNodes[0].nodeValue,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-one-domain",
{
domain: LONG_DOMAIN_NAME,
}
),
"Got the expected host permission string set as the nextNode value"
);
Assert.deepEqual(
// Calling getBoxQuads on the text node is expected to be returning
// one DOMQuad instance for each line the text node has been broken
// into (e.g. 3 DOMQuad instances when the long domain name forces
// the text node to be broken over 3 lines).
//
// This check is asserting that none of the lines the text node has
// been broken into has a width larger than the width of the parent
// element.
//
// NOTE: this assertion is expected to hit a failure if .webext-perm-granted
// or .addon-webext-perm-single-entry elements are missing the overflow-wrap
// CSS rule.
hostPermStringEl.childNodes[0]
.getBoxQuads()
.map(quad => quad.getBounds().width)
.filter(width => {
return width > hostPermStringEl.getBoundingClientRect().width;
}),
[],
"The host permission text node should NOT overflow the parent element"
);
},
},
{
msg: "Test text wrapping on long domain name in domains list",
id: "one-long-domain-in-domains-list",
domainsListLength: 10,
permissions: [`*://${LONG_DOMAIN_NAME}/*`],
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
const domainsListEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl.querySelector(
".webext-perm-domains-list"
)
: popupContentEl.permsListEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(
domainsListEl,
"Expect domains list element to be found inside the permission list element"
);
Assert.ok(
BrowserTestUtils.isVisible(domainsListEl),
"Expect the domains list element to be visible"
);
Assert.equal(
domainsListEl.firstElementChild.childNodes[0].nodeType,
domainsListEl.TEXT_NODE,
"Found text node for the long domain name item part of the domain list item"
);
Assert.equal(
domainsListEl.firstElementChild.childNodes[0].nodeValue,
LONG_DOMAIN_NAME,
"Got the expected domain name set on the first domain list item"
);
Assert.deepEqual(
// This check is asserting that none of the lines the text node has
// been broken into has a width larger than the width of the parent
// element.
//
// NOTE: this assertion is expected to hit a failure if .webext-perm-domains-list
// list items elements are overflowing (e.g. if it is not inheriting the
// overflow-wrap CSS rule from its ascending notes and doesn't have one set on
// its own).
domainsListEl.firstElementChild.childNodes[0]
.getBoxQuads()
.map(quad => quad.getBounds().width)
.filter(width => {
return (
width >
domainsListEl.firstElementChild.getBoundingClientRect().width
);
}),
[],
"The domain name text node should NOT overflow the parent element"
);
},
},
{
msg: "Test wildcard subdomains shown as single host permission",
id: "with-wildcard-subdomains",
domainsListLength: 0,
permissions: ["*://*.example.com/*", "*://example.com/*"],
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
const hostPermStringEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl
: popupContentEl.permsListEl.querySelector("li.webext-perm-granted");
Assert.equal(
hostPermStringEl.textContent,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-one-domain",
{
domain: "example.com",
}
),
"Expected *.example.com and example.com host permissions to be reported as a single domain permission string"
);
},
},
{
msg: "Test wildcard subdomains in domains list",
id: "with-wildcard-subdomains",
domainsListLength: 0,
permissions: [
"*://*.example.com/*",
"*://example.com/*`",
"*://*.example.org/*",
"*://example.org/*",
],
verifyDialog(popupContentEl, noIncognitoCheckbox) {
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
assertPermsElVisibility(popupContentEl, noIncognitoCheckbox);
const domainsListEl = noIncognitoCheckbox
? popupContentEl.permsSingleEl.querySelector(
".webext-perm-domains-list"
)
: popupContentEl.permsListEl.querySelector(
".webext-perm-domains-list"
);
Assert.ok(
domainsListEl,
"Expect domains list element to be found inside the permission list element"
);
Assert.ok(
BrowserTestUtils.isVisible(domainsListEl),
"Expect the domains list element to be visible"
);
// Expect the domains list to only include 2 domains and the host permissions string
// to reflect that as well.
Assert.deepEqual(
Array.from(domainsListEl.querySelectorAll("li")).map(
el => el.textContent
),
["example.com", "example.org"],
"Got the expected domains listed in the domains list element"
);
Assert.equal(
domainsListEl.previousElementSibling.value,
PERMISSION_L10N.formatValueSync(
"webext-perms-host-description-multiple-domains",
{
domainCount: 2,
}
),
"Got the expected host permission string on extension with 2 granted domain"
);
},
},
];
for (const testCase of TEST_CASES) {
// Repeat each test without and without the private browsing checkbox
// (to test the dialog when the host permissions is expected to be part of a list
// or to be the only permissions listed in the dialog).
for (const incognito of ["spanning", "not_allowed"]) {
const noIncognitoCheckbox = incognito === "not_allowed";
info(
`${testCase.msg} ${
noIncognitoCheckbox ? "and no other permissions" : ""
}`
);
const xpi = createTestExtensionXPI({
...testCase,
id: `${testCase.id}-with-incognito-${incognito}@test-ext`,
incognito,
});
await BrowserTestUtils.withNewTab("about:blank", async () => {
const dialogPromise = promisePopupNotificationShown(
"addon-webext-permissions"
);
gURLBar.value = xpi.path;
gURLBar.focus();
EventUtils.synthesizeKey("KEY_Enter");
const popupContentEl = await dialogPromise;
testCase.verifyDialog(popupContentEl, noIncognitoCheckbox);
let popupHiddenPromise = BrowserTestUtils.waitForEvent(
window.PopupNotifications.panel,
"popuphidden"
);
// hide the panel (this simulates the user dismissing it)
popupContentEl.closest("panel").hidePopup();
await popupHiddenPromise;
});
}
}
await SpecialPowers.popPrefEnv();
});