Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
-
CI results over the last 21 days
- Manifest: browser/components/preferences/tests/permissions/browser-srd.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
const SECTION_PREF = "browser.preferences.notifications-section.enabled";
const CLOSED_DELIVERY_PREF = "app.backgroundNotifications.helper.available";
// The www. prefix is stripped for display, so these sort alpha, beta, gamma.
const SITES = [
];
const ORIGINS = SITES.map(([origin]) => origin);
const SORTED_HOSTS = [
"alpha.example.com",
"beta.example.com",
"gamma.example.org",
];
function addPermission(origin, capability) {
PermissionTestUtils.add(origin, "desktop-notification", capability);
}
function removePermission(origin) {
PermissionTestUtils.remove(origin, "desktop-notification");
}
function getRows(control) {
return [...control.querySelectorAll("moz-box-item:not([slot='header'])")];
}
function getTestRows(control) {
return getRows(control).filter(row =>
ORIGINS.includes(row.getAttribute("value"))
);
}
function getStateL10nIds(rows) {
return rows.map(row =>
row.querySelector("[slot='actions']")?.getAttribute("data-l10n-id")
);
}
async function openNotificationsPane() {
let tab = await openPrefsTab("notifications");
let win = tab.linkedBrowser.contentWindow;
let control = await settingControlRenders("notification-sites-list", win);
await control.controlEl.updateComplete;
return { tab, win, control };
}
add_setup(async function () {
await SpecialPowers.pushPrefEnv({ set: [[SECTION_PREF, true]] });
for (let [origin, capability] of SITES) {
addPermission(origin, capability);
}
registerCleanupFunction(() => {
for (let origin of ORIGINS) {
removePermission(origin);
}
});
});
add_task(async function test_hidden_without_pref() {
await SpecialPowers.pushPrefEnv({ set: [[SECTION_PREF, false]] });
let tab = await openPrefsTab("permissionsData");
let win = tab.linkedBrowser.contentWindow;
let entry = await settingControlRenders("notificationSettingsEntry", win);
ok(entry.hidden, "Entry row is hidden while the section pref is off");
ok(
entry.closest("setting-group").hasAttribute("data-hidden-by-setting-group"),
"Its card is hidden too, so the shipped page is unchanged"
);
BrowserTestUtils.removeTab(tab);
info("The sub-page is not reachable by URL either");
tab = await openPrefsTab("notifications");
win = tab.linkedBrowser.contentWindow;
ok(
!win.document.querySelector('[data-category="paneNotifications"]'),
"No Notifications pane was registered"
);
isnot(
win.history.state?.category,
"notifications",
"Deep linking falls back to the default pane"
);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
add_task(async function test_hidden_without_parent() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.settings-redesign.enabled", false],
["browser.settings-redesign.permissionsData.enabled", false],
[SECTION_PREF, true],
],
});
let tab = await openPrefsTab("notifications");
let win = tab.linkedBrowser.contentWindow;
ok(
!win.document.querySelector('[data-category="paneNotifications"]'),
"No orphan Notifications pane is registered while its parent is disabled"
);
ok(
!win.document.getElementById("notification-sites-list"),
"No orphan notification list is rendered while its parent is disabled"
);
isnot(
win.history.state?.category,
"notifications",
"Deep linking falls back when the Permissions and data parent is disabled"
);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
add_task(async function test_reachable_with_parent_only() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.settings-redesign.enabled", false],
["browser.settings-redesign.permissionsData.enabled", true],
[SECTION_PREF, true],
],
});
let tab = await openPrefsTab("notifications");
let win = tab.linkedBrowser.contentWindow;
let pane = win.document.querySelector('[data-category="paneNotifications"]');
ok(
pane && BrowserTestUtils.isVisible(pane),
"Notifications is visible when only its parent section is enabled"
);
is(
win.history.state?.category,
"notifications",
"The Notifications deep link works whenever its Permissions and data parent is enabled"
);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
add_task(async function test_entry_card_and_deep_link() {
let tab = await openPrefsTab("permissionsData");
let win = tab.linkedBrowser.contentWindow;
let entry = await settingControlRenders("notificationSettingsEntry", win);
await entry.controlEl.updateComplete;
ok(!entry.hidden, "Entry row is shown while the section pref is on");
is(
entry.controlEl.label,
"Manage notification settings",
"The entry row is labelled for managing existing settings"
);
is(
entry.controlEl.description,
"3 websites",
"Subtitle counts only origins with an allowed or blocked decision"
);
ok(
!getSettingControl("notificationSettingsButton", win).hidden,
);
let paneShown = waitForPaneChange("notifications", win);
synthesizeClick(entry.controlEl);
await paneShown;
BrowserTestUtils.removeTab(tab);
info("about:preferences#notifications reaches the sub-page directly");
let { tab: deepLinkTab } = await openNotificationsPane();
BrowserTestUtils.removeTab(deepLinkTab);
});
add_task(async function test_site_list() {
let { tab, win, control } = await openNotificationsPane();
Assert.deepEqual(
getTestRows(control).map(row => row.label),
SORTED_HOSTS,
"Every origin with a stored decision is listed, sorted by display host"
);
Assert.deepEqual(
getStateL10nIds(getTestRows(control)),
[
"notifications-state-never",
"notifications-state-allow",
"notifications-state-allow",
],
"Blocked sites appear inline, showing their stored state in the state column"
);
let header = control.querySelector("moz-box-item[slot='header']");
is(
header.getAttribute("data-l10n-id"),
"notifications-list-header",
"The website column is headed in the singular while there are rows"
);
let stateHeading = header.querySelector(
"[data-l10n-id='notifications-list-header-state']"
);
ok(
stateHeading,
"The header row labels the state column while there are rows to state"
);
// The state heading is slotted, so it misses the class that weights the
// item's own label and has to be weighted separately.
is(
win.getComputedStyle(stateHeading).fontWeight,
win.getComputedStyle(header.shadowRoot.querySelector(".label")).fontWeight,
"Both column headings are weighted the same"
);
info("The list excludes the same permissions the dialog excludes");
// Granted in a private window, so it never persists.
let privatePrincipal = Services.scriptSecurityManager.createContentPrincipal(
{ privateBrowsingId: 1 }
);
PermissionTestUtils.add(
privatePrincipal,
"desktop-notification",
Services.perms.ALLOW_ACTION
);
// Neither allowed nor blocked, so there is no decision to state.
// A permission that does belong in the list, so the assertion below runs
// against a refreshed list rather than one that has not caught up.
await BrowserTestUtils.waitForMutationCondition(
control,
{ childList: true, subtree: true },
() =>
getRows(control).some(
)
);
Assert.deepEqual(
getRows(control).map(row => row.label),
[...SORTED_HOSTS, "zeta.example.com"],
"Neither the private grant nor the prompt permission is listed"
);
PermissionTestUtils.remove(privatePrincipal, "desktop-notification");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_live_refresh() {
let { tab, control } = await openNotificationsPane();
let hasDelta = () =>
getRows(control).some(
);
info("A permission granted elsewhere appears without a reload");
await BrowserTestUtils.waitForMutationCondition(
control,
{ childList: true, subtree: true },
hasDelta
);
await BrowserTestUtils.waitForMutationCondition(
control,
{ childList: true, subtree: true },
() => !hasDelta()
);
ok(true, "Revoking it removes the row again");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_search() {
let { tab, win, control } = await openNotificationsPane();
let filter = win.document.getElementById("notificationsFilter");
async function search(query, predicate) {
filter.focus();
filter.inputEl.select();
EventUtils.sendString(query, win);
await BrowserTestUtils.waitForMutationCondition(
control,
{ childList: true, subtree: true },
predicate
);
}
await search("beta", () => getTestRows(control).length == 1);
Assert.deepEqual(
getTestRows(control).map(row => row.label),
["beta.example.com"],
"Typing filters the list to matching hosts"
);
await search("ëxample", () => getTestRows(control).length == 3);
ok(true, "Matching ignores diacritics as well as case");
await search("nothinghere", () =>
control.querySelector("#notifications-no-results")
);
is(getTestRows(control).length, 0, "No rows are left when nothing matches");
is(
control
.querySelector("moz-box-item[slot='header']")
.getAttribute("data-l10n-id"),
"notifications-list-header-empty",
"The header over no results is the one used over an empty list"
);
let noResults = control.querySelector("#notifications-no-results");
await win.document.l10n.translateElements([noResults]);
await noResults.updateComplete;
is(
noResults.label,
"No website notifications match your search.",
"The no-results message describes a search without matching websites"
);
filter.clear();
await BrowserTestUtils.waitForMutationCondition(
control,
{ childList: true, subtree: true },
() => getTestRows(control).length == 3
);
ok(true, "Clearing the search restores the full list");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_empty_state() {
for (let origin of ORIGINS) {
removePermission(origin);
}
let { tab, win, control } = await openNotificationsPane();
ok(
control.querySelector("#notifications-empty"),
"The card shows an empty state rather than a blank list"
);
let empty = control.querySelector("#notifications-empty");
await win.document.l10n.translateElements([empty]);
await empty.updateComplete;
is(
empty.label,
"No website notifications added.",
"The empty-list message describes having no stored website notifications"
);
is(
control
.querySelector("moz-box-item[slot='header']")
?.getAttribute("data-l10n-id"),
"notifications-list-header-empty",
"The header row stays over the empty state, headed in the plural"
);
ok(
!control.querySelector("[data-l10n-id='notifications-list-header-state']"),
"But the state column is not labelled, having nothing to state"
);
ok(
win.document.getElementById("notificationsFilter"),
"The search box is still shown over an empty list"
);
let paneShown = waitForPaneChange("permissionsData", win);
win.location.hash = "permissionsData";
await paneShown;
let entry = await settingControlRenders("notificationSettingsEntry", win);
await entry.controlEl.updateComplete;
is(
entry.controlEl.label,
"Manage website notification settings",
"The entry row keeps its empty-state label"
);
ok(
!entry.controlEl.description,
"The entry row drops its subtitle rather than showing a count of zero"
);
BrowserTestUtils.removeTab(tab);
for (let [origin, capability] of SITES) {
addPermission(origin, capability);
}
});
add_task(async function test_state_label_with_closed_delivery() {
await SpecialPowers.pushPrefEnv({ set: [[CLOSED_DELIVERY_PREF, true]] });
let { tab, control } = await openNotificationsPane();
Assert.deepEqual(
getStateL10nIds(getTestRows(control)),
[
"notifications-state-never",
"notifications-state-always",
"notifications-state-always",
],
"Allowed sites read Always where closed-browser delivery is available"
);
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
});
// Last, because the permission manager refuses to remove EXPIRE_POLICY
// permissions, so this task has to clear the whole store to clean up.
add_task(async function test_policy_duplicate_excluded() {
for (let origin of [
]) {
PermissionTestUtils.add(
origin,
"desktop-notification",
Services.perms.ALLOW_ACTION,
Services.perms.EXPIRE_POLICY,
0
);
}
let { tab, control } = await openNotificationsPane();
Assert.deepEqual(
getRows(control).map(row => row.label),
[...SORTED_HOSTS, "policy.example.com"],
"A policy permission repeated for the trailing dot host is listed once"
);
BrowserTestUtils.removeTab(tab);
Services.perms.removeAll();
});