commit 5704ef88a60d9c9f9d4951b8af933f4da78ab565
parent 7af4ff6038510c3e7faa91ac8c62a15f8aa7bf1a
Author: kpatenio <kpatenio@mozilla.com>
Date: Mon, 15 Dec 2025 23:58:41 +0000
Bug 1996945 — implement an Add button in the permissions dialog for ipp-vpn site exclusions. r=mstriemer,fluent-reviewers,ip-protection-reviewers,rking,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D276330
Diffstat:
5 files changed, 135 insertions(+), 25 deletions(-)
diff --git a/browser/components/preferences/dialogs/permissions.js b/browser/components/preferences/dialogs/permissions.js
@@ -83,6 +83,7 @@ var gPermissionManager = {
* @param {boolean} params.sessionVisible Display the "Allow for Session" button in the dialog (Only for Cookie & HTTPS-Only permissions)
* @param {boolean} params.allowVisible Display the "Allow" button in the dialog
* @param {boolean} params.disableETPVisible Display the "Add Exception" button in the dialog (Only for ETP permissions)
+ * @param {boolean} params.addVisible Display the "Add" button in the dialog (Only for ipp-vpn permissions)
* @param {boolean} params.hideStatusColumn Hide the "Status" column in the dialog
* @param {boolean} params.forcedHTTP Save inputs whose URI has a HTTPS scheme with a HTTP scheme (Used by HTTPS-Only)
* @param {number} params.capabilityFilter Display permissions that have the specified capability only. See Ci.nsIPermissionManager.
@@ -106,6 +107,7 @@ var gPermissionManager = {
this._btnAllow = document.getElementById("btnAllow");
this._btnHttpsOnlyOff = document.getElementById("btnHttpsOnlyOff");
this._btnHttpsOnlyOffTmp = document.getElementById("btnHttpsOnlyOffTmp");
+ this._btnAdd = document.getElementById("btnAdd");
this._capabilityFilter = params.capabilityFilter;
@@ -120,7 +122,8 @@ var gPermissionManager = {
params.blockVisible ||
params.sessionVisible ||
params.allowVisible ||
- params.disableETPVisible;
+ params.disableETPVisible ||
+ params.addVisible;
this._urlField = document.getElementById("url");
this._urlField.value = params.prefilledHost;
@@ -145,6 +148,7 @@ var gPermissionManager = {
params.sessionVisible && this._type == "https-only-load-insecure"
);
document.getElementById("btnAllow").hidden = !params.allowVisible;
+ document.getElementById("btnAdd").hidden = !params.addVisible;
this.onHostInput(this._urlField);
@@ -236,6 +240,10 @@ var gPermissionManager = {
Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION
);
break;
+ case "btnAdd":
+ // This button is for ipp-vpn, which only supports
+ // site exclusions at this time.
+ gPermissionManager.addPermission(Ci.nsIPermissionManager.DENY_ACTION);
}
});
},
@@ -559,6 +567,8 @@ var gPermissionManager = {
document.getElementById("btnHttpsOnlyOff").click();
} else if (!document.getElementById("btnDisableETP").hidden) {
document.getElementById("btnDisableETP").click();
+ } else if (!document.getElementById("btnAdd").hidden) {
+ document.getElementById("btnAdd").click();
}
}
},
@@ -574,6 +584,7 @@ var gPermissionManager = {
this._btnDisableETP.disabled =
this._btnDisableETP.hidden || !siteField.value;
this._btnAllow.disabled = this._btnAllow.hidden || !siteField.value;
+ this._btnAdd.disabled = this._btnAdd.hidden || !siteField.value;
},
_setRemoveButtonState() {
diff --git a/browser/components/preferences/dialogs/permissions.xhtml b/browser/components/preferences/dialogs/permissions.xhtml
@@ -87,6 +87,7 @@
disabled="true"
data-l10n-id="permissions-button-off-temporarily"
/>
+ <button id="btnAdd" disabled="true" data-l10n-id="permissions-add" />
</hbox>
<separator class="thin" />
<listheader>
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
@@ -1438,7 +1438,7 @@ Preferences.addSetting({
ipProtectionVisible.value && ipProtectionSiteExceptionsFeatureEnabled.value,
onUserClick() {
let params = {
- blockVisible: true,
+ addVisible: true,
hideStatusColumn: true,
prefilledHost: "",
permissionType: "ipp-vpn",
diff --git a/browser/components/preferences/tests/browser_privacy_ipprotection.js b/browser/components/preferences/tests/browser_privacy_ipprotection.js
@@ -12,6 +12,8 @@ const AUTOSTART_FEATURE_ENABLED_PREF =
"browser.ipProtection.features.autoStart";
const AUTOSTART_PREF = "browser.ipProtection.autoStartEnabled";
const AUTOSTART_PRIVATE_PREF = "browser.ipProtection.autoStartPrivateEnabled";
+const ONBOARDING_MESSAGE_MASK_PREF =
+ "browser.ipProtection.onboardingMessageMask";
const SECTION_ID = "dataIPProtectionGroup";
@@ -33,6 +35,17 @@ async function setupVpnPrefs({
});
}
+function testSettingsGroupVisible(browser, sectionId) {
+ let section = browser.contentDocument.getElementById(sectionId);
+ let settingGroup = section.querySelector(
+ `setting-group[groupid="ipprotection"]`
+ );
+ is_element_visible(section, "#dataIPProtectionGroup is shown");
+ is_element_visible(settingGroup, "ipprotection setting group is shown");
+
+ return settingGroup;
+}
+
// Test the section is hidden on page load if the variant pref is set to an ineligible experiment.
add_task(
async function test_section_removed_when_set_to_ineligible_experiment_pref() {
@@ -58,8 +71,7 @@ add_task(
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:preferences#privacy" },
async function (browser) {
- let section = browser.contentDocument.getElementById(SECTION_ID);
- is_element_visible(section, "#dataIPProtectionGroup is shown");
+ testSettingsGroupVisible(browser, SECTION_ID);
}
);
}
@@ -72,13 +84,7 @@ add_task(async function test_exceptions_settings() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:preferences#privacy" },
async function (browser) {
- let section = browser.contentDocument.getElementById(SECTION_ID);
- let settingGroup = section.querySelector(
- `setting-group[groupid="ipprotection"]`
- );
- is_element_visible(section, "#dataIPProtectionGroup is shown");
- is_element_visible(settingGroup, "ipprotection setting group is shown");
-
+ let settingGroup = testSettingsGroupVisible(browser, SECTION_ID);
let siteExceptionsGroup = settingGroup?.querySelector(
"#ipProtectionExceptions"
);
@@ -95,6 +101,106 @@ add_task(async function test_exceptions_settings() {
);
});
+// Test that we show the "Add" button in the site exceptions permission dialog
+// and correctly add site exclusions.
+add_task(async function test_exclusions_add_button() {
+ const PERM_NAME = "ipp-vpn";
+ await setupVpnPrefs({ feature: "beta", siteExceptions: true });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async function (browser) {
+ let settingGroup = testSettingsGroupVisible(browser, SECTION_ID);
+ let siteExceptionsGroup = settingGroup?.querySelector(
+ "#ipProtectionExceptions"
+ );
+ let exceptionAllListButton = siteExceptionsGroup?.querySelector(
+ "#ipProtectionExceptionAllListButton"
+ );
+ is_element_visible(
+ exceptionAllListButton,
+ "Button for list of exclusions is shown"
+ );
+
+ // Clear ipp-vpn to start with 0 exclusions
+ Services.perms.removeByType(PERM_NAME);
+
+ // Let's load the dialog
+ let promiseSubDialogLoaded = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/permissions.xhtml"
+ );
+
+ exceptionAllListButton.click();
+
+ const win = await promiseSubDialogLoaded;
+
+ let addButton = win.document.getElementById("btnAdd");
+ Assert.ok(addButton, "Add button exists");
+ Assert.ok(BrowserTestUtils.isVisible(addButton), "Add button is visible");
+ Assert.ok(addButton.disabled, "Add button is disabled");
+
+ // Now let's click the Add button to add a new exclusion
+ let permissionsBox = win.document.getElementById("permissionsBox");
+ let siteListUpdatedPromise = BrowserTestUtils.waitForMutationCondition(
+ permissionsBox,
+ { subtree: true, childList: true },
+ () => {
+ return permissionsBox.children.length;
+ }
+ );
+
+ // Set up a mock url input value
+ let urlField = win.document.getElementById("url");
+ Assert.ok(urlField, "Dialog url field exists");
+ const site1 = "https://example.com";
+ urlField.focus();
+
+ EventUtils.sendString(site1, win);
+ Assert.ok(!addButton.disabled, "Add button is enabled");
+
+ addButton.click();
+
+ await siteListUpdatedPromise;
+
+ permissionsBox = win.document.getElementById("permissionsBox");
+ Assert.equal(
+ permissionsBox.children.length,
+ 1,
+ "Should have 1 site listed as an exclusion"
+ );
+
+ let shownSite1 = permissionsBox.children[0];
+ Assert.equal(
+ shownSite1.getAttribute("origin"),
+ site1,
+ "Should match inputted site in the list of sites"
+ );
+
+ // Apply the changes
+ let saveButton = win.document.querySelector("dialog").getButton("accept");
+ Assert.ok(saveButton, "Save button is shown");
+
+ saveButton.click();
+
+ let exclusions = Services.perms.getAllByTypes([PERM_NAME]);
+ Assert.equal(
+ exclusions.length,
+ 1,
+ "Should have 1 exclusion after pressing the Add button"
+ );
+ Assert.equal(
+ exclusions[0]?.principal.siteOrigin,
+ site1,
+ "Should match the inputted site"
+ );
+
+ // Clean up
+ Services.perms.removeByType(PERM_NAME);
+ Services.prefs.clearUserPref(ONBOARDING_MESSAGE_MASK_PREF);
+ }
+ );
+});
+
// Test that autostart checkboxes exist and map to the correct preferences
add_task(async function test_autostart_checkboxes() {
await setupVpnPrefs({
@@ -107,13 +213,7 @@ add_task(async function test_autostart_checkboxes() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:preferences#privacy" },
async function (browser) {
- let section = browser.contentDocument.getElementById(SECTION_ID);
- let settingGroup = section.querySelector(
- `setting-group[groupid="ipprotection"]`
- );
- is_element_visible(section, "#dataIPProtectionGroup is shown");
- is_element_visible(settingGroup, "ipprotection setting group is shown");
-
+ let settingGroup = testSettingsGroupVisible(browser, SECTION_ID);
let autoStartSettings = settingGroup?.querySelector(
"#ipProtectionAutoStart"
);
@@ -150,13 +250,7 @@ add_task(async function test_additional_links() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:preferences#privacy" },
async function (browser) {
- let section = browser.contentDocument.getElementById(SECTION_ID);
- let settingGroup = section.querySelector(
- `setting-group[groupid="ipprotection"]`
- );
- is_element_visible(section, "#dataIPProtectionGroup is shown");
- is_element_visible(settingGroup, "ipprotection setting group is shown");
-
+ let settingGroup = testSettingsGroupVisible(browser, SECTION_ID);
let additionalLinks = settingGroup?.querySelector(
"#ipProtectionAdditionalLinks"
);
diff --git a/browser/locales/en-US/browser/preferences/permissions.ftl b/browser/locales/en-US/browser/preferences/permissions.ftl
@@ -28,6 +28,10 @@ permissions-allow =
.label = Allow
.accesskey = A
+permissions-add =
+ .label = Add
+ .accesskey = A
+
permissions-button-off =
.label = Turn Off
.accesskey = O