commit 86937429cf28205d79998d147380193138a6b7c3
parent c06c2d185f7043763f3025d700a1dc412acba95c
Author: kpatenio <kpatenio@mozilla.com>
Date: Tue, 21 Oct 2025 17:04:27 +0000
Bug 1993322 — add basic site exceptions controls to IP Protection settings section. r=ip-protection-reviewers,fluent-reviewers,mstriemer,bolsson,desktop-theme-reviewers,fchasen
Differential Revision: https://phabricator.services.mozilla.com/D268729
Diffstat:
5 files changed, 257 insertions(+), 10 deletions(-)
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
@@ -1486,11 +1486,38 @@ let SETTINGS_CONFIG = {
supportPage: "ip-protection",
items: [
{
- id: "ipProtectionPlaceholderMessage",
- control: "moz-message-bar",
+ id: "ipProtectionExceptionsMode",
+ l10nId: "ip-protection-site-exceptions",
+ control: "moz-radio-group",
controlAttrs: {
- message: "This is a placeholder for the IP Protection section",
+ ".headingLevel": 3,
},
+ options: [
+ {
+ id: "ipProtectionExceptionRadioAll",
+ value: "all",
+ l10nId: "ip-protection-site-exceptions-all-sites-radio",
+ items: [
+ {
+ id: "ipProtectionExceptionAllListButton",
+ l10nId: "ip-protection-site-exceptions-all-sites-button",
+ control: "moz-box-button",
+ },
+ ],
+ },
+ {
+ id: "ipProtectionExceptionRadioSelect",
+ value: "select",
+ l10nId: "ip-protection-site-exceptions-select-sites-radio",
+ items: [
+ {
+ id: "ipProtectionExceptionSelectListButton",
+ l10nId: "ip-protection-site-exceptions-select-sites-button",
+ control: "moz-box-button",
+ },
+ ],
+ },
+ ],
},
],
},
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
@@ -212,6 +212,7 @@ Preferences.addAll([
// Firefox VPN
{ id: "browser.ipProtection.variant", type: "string" },
+ { id: "browser.ipProtection.exceptionsMode", type: "string" },
// Media
{ id: "media.autoplay.default", type: "int" },
@@ -1260,11 +1261,35 @@ Preferences.addSetting({
pref: "browser.ipProtection.variant",
get: prefVal => prefVal == "beta",
});
+// This setting also affects the radio group for site exceptions
Preferences.addSetting({
- id: "ipProtectionPlaceholderMessage",
+ id: "ipProtectionExceptionsMode",
+ pref: "browser.ipProtection.exceptionsMode",
deps: ["ipProtectionVisible"],
visible: ({ ipProtectionVisible }) => ipProtectionVisible.value,
});
+Preferences.addSetting({
+ id: "ipProtectionExceptionAllListButton",
+ deps: ["ipProtectionVisible", "ipProtectionExceptionsMode"],
+ visible: ({ ipProtectionVisible, ipProtectionExceptionsMode }) =>
+ ipProtectionVisible.value && ipProtectionExceptionsMode.value == "all",
+ onUserClick() {
+ // TODO: show UI based on current exception mode selected (Bug 1993334)
+ // We can read the target id to verify the button type and open a dialog
+ // with gSubDialog.open
+ },
+});
+Preferences.addSetting({
+ id: "ipProtectionExceptionSelectListButton",
+ deps: ["ipProtectionVisible", "ipProtectionExceptionsMode"],
+ visible: ({ ipProtectionVisible, ipProtectionExceptionsMode }) =>
+ ipProtectionVisible.value && ipProtectionExceptionsMode.value == "select",
+ onUserClick() {
+ // TODO: show UI based on current exception mode selected (Bug 1993334)
+ // We can read the target id to verify the button type and open a dialog
+ // with gSubDialog.open
+ },
+});
// Study opt out
if (AppConstants.MOZ_DATA_REPORTING) {
diff --git a/browser/components/preferences/tests/browser_privacy_ipprotection.js b/browser/components/preferences/tests/browser_privacy_ipprotection.js
@@ -6,15 +6,23 @@
"use strict";
const FEATURE_PREF = "browser.ipProtection.variant";
+const MODE_PREF = "browser.ipProtection.exceptionsMode";
const SECTION_ID = "dataIPProtectionGroup";
+async function setupVpnPrefs({ feature, mode = "all" }) {
+ return SpecialPowers.pushPrefEnv({
+ set: [
+ [FEATURE_PREF, feature],
+ [MODE_PREF, mode],
+ ],
+ });
+}
+
// 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() {
- await SpecialPowers.pushPrefEnv({
- set: [[FEATURE_PREF, "alpha"]],
- });
+ await setupVpnPrefs({ feature: "alpha" });
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:preferences#privacy" },
@@ -31,9 +39,7 @@ add_task(
// Test the section is shown on page load if the variant pref is set to an eligible experiment
add_task(
async function test_section_shown_when_set_to_eligible_experiment_pref() {
- await SpecialPowers.pushPrefEnv({
- set: [[FEATURE_PREF, "beta"]],
- });
+ await setupVpnPrefs({ feature: "beta" });
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:preferences#privacy" },
@@ -46,3 +52,174 @@ add_task(
await SpecialPowers.popPrefEnv();
}
);
+
+// Test the site exceptions controls load correctly with mode set to "all"
+add_task(async function test_exceptions_load_with_all_mode() {
+ await setupVpnPrefs({ feature: "beta" });
+
+ 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 siteExceptionsRadioGroup = settingGroup?.querySelector(
+ "#ipProtectionExceptionsMode"
+ );
+ is_element_visible(
+ siteExceptionsRadioGroup,
+ "Site exceptions radio group is shown"
+ );
+
+ let exceptionAllRadioButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionRadioAll"
+ );
+ let exceptionSelectRadioButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionRadioSelect"
+ );
+ Assert.ok(
+ exceptionAllRadioButton?.checked,
+ "The 'all' radio button should be checked"
+ );
+ Assert.ok(
+ !exceptionSelectRadioButton?.checked,
+ "The 'select' radio button should not be checked"
+ );
+
+ let exceptionAllListButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionAllListButton"
+ );
+ let exceptionSelectListButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionSelectListButton"
+ );
+ is_element_visible(
+ exceptionAllListButton,
+ "Button for list of exclusions is shown"
+ );
+ is_element_hidden(
+ exceptionSelectListButton,
+ "Button for list of inclusions is hidden"
+ );
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the site exceptions controls load correctly with mode set to "select"
+add_task(async function test_exceptions_with_select_mode() {
+ await setupVpnPrefs({ feature: "beta", mode: "select" });
+
+ 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 siteExceptionsRadioGroup = settingGroup?.querySelector(
+ "#ipProtectionExceptionsMode"
+ );
+ is_element_visible(
+ siteExceptionsRadioGroup,
+ "Site exceptions radio group is shown"
+ );
+
+ let exceptionAllRadioButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionRadioAll"
+ );
+ let exceptionSelectRadioButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionRadioSelect"
+ );
+ Assert.ok(
+ !exceptionAllRadioButton?.checked,
+ "The 'all' radio button should not be checked"
+ );
+ Assert.ok(
+ exceptionSelectRadioButton?.checked,
+ "The 'select' radio button should be checked"
+ );
+
+ let exceptionAllListButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionAllListButton"
+ );
+ let exceptionSelectListButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionSelectListButton"
+ );
+ is_element_hidden(
+ exceptionAllListButton,
+ "Button for list of exclusions is hidden"
+ );
+ is_element_visible(
+ exceptionSelectListButton,
+ "Button for list of inclusions is shown"
+ );
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test the site exceptions controls and pref update correctly after selecting another mode option.
+add_task(async function test_exceptions_change_mode_and_buttons() {
+ await setupVpnPrefs({ feature: "beta" });
+
+ 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 siteExceptionsRadioGroup = settingGroup?.querySelector(
+ "#ipProtectionExceptionsMode"
+ );
+ is_element_visible(
+ siteExceptionsRadioGroup,
+ "Site exceptions radio group is shown"
+ );
+
+ let exceptionAllRadioButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionRadioAll"
+ );
+ let exceptionSelectRadioButton = siteExceptionsRadioGroup?.querySelector(
+ "#ipProtectionExceptionRadioSelect"
+ );
+
+ // Change mode by clicking "select" button
+ exceptionSelectRadioButton.click();
+
+ Assert.ok(
+ !exceptionAllRadioButton?.checked,
+ "The 'all' radio button should not be checked"
+ );
+ Assert.ok(
+ exceptionSelectRadioButton?.checked,
+ "The 'select' radio button should be checked"
+ );
+
+ let mode = Services.prefs.getStringPref(MODE_PREF);
+ Assert.equal(
+ mode,
+ "select",
+ `Mode should now be "select" instead of "all"`
+ );
+
+ Services.prefs.clearUserPref(MODE_PREF);
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/locales-preview/ipProtection.ftl b/browser/locales-preview/ipProtection.ftl
@@ -81,4 +81,21 @@ ip-protection-description =
.description = Hides your location and adds extra encryption to your browsing in { -brand-short-name }.
ip-protection-learn-more = Learn more
+ip-protection-site-exceptions =
+ .label = Where to use VPN
+ .description = Use VPN for all websites except ones you choose, or for select specific ones.
+ip-protection-site-exceptions-all-sites-radio =
+ .label = All websites (default)
+ip-protection-site-exceptions-all-sites-button =
+ .label = { -firefox-vpn-brand-name } is off for these websites
+ .description = No websites added yet
+
+# "Select" is an adjective here to describe a setting that allows running the VPN on certain sites only.
+# Not to be confused with the action of selecting a site, which is not at all applicable to this setting.
+ip-protection-site-exceptions-select-sites-radio =
+ .label = Select websites
+ip-protection-site-exceptions-select-sites-button =
+ .label = { -firefox-vpn-brand-name } is on for these websites
+ .description = No websites added yet
+
##
diff --git a/browser/themes/shared/preferences/preferences.css b/browser/themes/shared/preferences/preferences.css
@@ -13,6 +13,7 @@
*/
--heading-font-size-xlarge: var(--font-size-xlarge);
--heading-font-size-large: var(--font-size-large);
+ --heading-font-size-medium: var(--font-size-medium);
user-select: text;
}