tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mbrowser/components/preferences/dialogs/permissions.js | 13++++++++++++-
Mbrowser/components/preferences/dialogs/permissions.xhtml | 1+
Mbrowser/components/preferences/privacy.js | 2+-
Mbrowser/components/preferences/tests/browser_privacy_ipprotection.js | 140++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mbrowser/locales/en-US/browser/preferences/permissions.ftl | 4++++
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