tor-browser

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

commit 38fe31e6d88fc15a3f332f248de9a19e6d2df17f
parent 1f4c1dae040f99d7a89cc3d7cd0e144bd4377a3f
Author: Rebecca King <rking@mozilla.com>
Date:   Wed,  7 Jan 2026 14:31:57 +0000

Bug 2007184 - Add connection button to toggle VPN on and off - r=ip-protection-reviewers,fluent-reviewers,flod,kpatenio

Differential Revision: https://phabricator.services.mozilla.com/D277463

Diffstat:
Mbrowser/components/ipprotection/content/ipprotection-status-card.mjs | 38+++++++++++++++++++++++++++++++++++++-
Mbrowser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/locales-preview/ipProtection.ftl | 5+++++
3 files changed, 171 insertions(+), 1 deletion(-)

diff --git a/browser/components/ipprotection/content/ipprotection-status-card.mjs b/browser/components/ipprotection/content/ipprotection-status-card.mjs @@ -24,6 +24,7 @@ export default class IPProtectionStatusCard extends MozLitElement { static queries = { statusGroupEl: "#status-card", connectionToggleEl: "#connection-toggle", + connectionButtonEl: "#connection-toggle-button", locationEl: "#location-wrapper", siteSettingsEl: "ipprotection-site-settings-control", }; @@ -84,6 +85,29 @@ export default class IPProtectionStatusCard extends MozLitElement { this._toggleEnabled = isEnabled; } + // TODO: Move button handling logic and button to new ipprotection-status-box component in Bug 2008854 + handleOnOffButtonClick() { + let isEnabled = !this._toggleEnabled; + + if (isEnabled) { + this.dispatchEvent( + new CustomEvent(this.TOGGLE_ON_EVENT, { + bubbles: true, + composed: true, + }) + ); + } else { + this.dispatchEvent( + new CustomEvent(this.TOGGLE_OFF_EVENT, { + bubbles: true, + composed: true, + }) + ); + } + + this._toggleEnabled = isEnabled; + } + focus() { this.connectionToggleEl?.focus(); } @@ -135,6 +159,10 @@ export default class IPProtectionStatusCard extends MozLitElement { const toggleL10nId = this.protectionEnabled ? "ipprotection-toggle-active" : "ipprotection-toggle-inactive"; + const toggleButtonType = this.protectionEnabled ? "secondary" : "primary"; + const toggleButtonL10nId = this.protectionEnabled + ? "ipprotection-button-turn-vpn-off" + : "ipprotection-button-turn-vpn-on"; const siteSettingsTemplate = this.protectionEnabled ? this.siteSettingsTemplate() @@ -163,7 +191,15 @@ export default class IPProtectionStatusCard extends MozLitElement { ></moz-toggle> </moz-box-item> ${siteSettingsTemplate} - </moz-box-group>`; + </moz-box-group> + <moz-button + type=${toggleButtonType} + id="connection-toggle-button" + data-l10n-id=${toggleButtonL10nId} + @click=${this.handleOnOffButtonClick} + hidden + > + </moz-button>`; } siteSettingsTemplate() { diff --git a/browser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js b/browser/components/ipprotection/tests/browser/browser_ipprotection_status_card.js @@ -200,3 +200,132 @@ add_task(async function test_ipprotection_events_on_toggle() { await panelHiddenPromise; cleanupService(); }); + +/** + * Tests that the correct IPProtection events are dispatched on button click. + */ +add_task(async function test_ipprotection_events_on_button_click() { + // These events are different from the ones sent by + // ipprotection-status-card. The prefixed "IPProtection:" events + // actually change the connection state in the service when dispatched. + // If the IPProtection events are sent, then we know that the status-card + // events worked. + const userEnableEventName = "IPProtection:UserEnable"; + const userDisableEventName = "IPProtection:UserDisable"; + + // Reset service state. + cleanupService(); + IPProtectionService.updateState(); + + setupService({ + isSignedIn: true, + isEnrolledAndEntitled: true, + canEnroll: true, + proxyPass: { + status: 200, + error: undefined, + pass: makePass(), + }, + }); + await IPPEnrollAndEntitleManager.refetchEntitlement(); + + let button = document.getElementById(lazy.IPProtectionWidget.WIDGET_ID); + let panelView = PanelMultiView.getViewNode( + document, + lazy.IPProtectionWidget.PANEL_ID + ); + + let panelShownPromise = waitForPanelEvent(document, "popupshown"); + // Open the panel + button.click(); + await panelShownPromise; + + let content = panelView.querySelector(lazy.IPProtectionPanel.CONTENT_TAGNAME); + + Assert.ok( + BrowserTestUtils.isVisible(content), + "ipprotection content component should be present" + ); + + let statusCard = content.statusCardEl; + + await BrowserTestUtils.waitForMutationCondition( + content.shadowRoot, + { childList: true, subtree: true }, + () => content.statusCardEl + ); + + Assert.ok(statusCard, "Status card should be present"); + + let connectionButton = statusCard?.connectionButtonEl; + connectionButton.hidden = false; + Assert.ok( + connectionButton, + "Status card connection button should be present" + ); + + let startedProxyPromise = BrowserTestUtils.waitForEvent( + IPPProxyManager, + "IPPProxyManager:StateChanged", + false, + () => !!IPPProxyManager.activatedAt + ); + let enableEventPromise = BrowserTestUtils.waitForEvent( + window, + userEnableEventName + ); + + connectionButton.click(); + info("Clicked toggle to turn VPN on"); + + await Promise.all([startedProxyPromise, enableEventPromise]); + Assert.ok( + true, + "Enable event and proxy started event were found after clicking the toggle" + ); + + let userEnabledPref = Services.prefs.getBoolPref( + "browser.ipProtection.userEnabled", + false + ); + Assert.equal(userEnabledPref, true, "userEnabled pref should be set to true"); + + let stoppedProxyPromise = BrowserTestUtils.waitForEvent( + IPPProxyManager, + "IPPProxyManager:StateChanged", + false, + () => !IPPProxyManager.activatedAt + ); + let disableEventPromise = BrowserTestUtils.waitForEvent( + window, + userDisableEventName + ); + + connectionButton = statusCard?.connectionButtonEl; + connectionButton.click(); + info("Clicked toggle to turn VPN off"); + + await Promise.all([stoppedProxyPromise, disableEventPromise]); + Assert.ok( + true, + "Disable event and stopped proxy event were found after clicking the toggle" + ); + + userEnabledPref = Services.prefs.getBoolPref( + "browser.ipProtection.userEnabled", + true + ); + Assert.equal( + userEnabledPref, + false, + "userEnabled pref should be set to false" + ); + + connectionButton.hidden = true; + + // Close the panel + let panelHiddenPromise = waitForPanelEvent(document, "popuphidden"); + EventUtils.synthesizeKey("KEY_Escape"); + await panelHiddenPromise; + cleanupService(); +}); diff --git a/browser/locales-preview/ipProtection.ftl b/browser/locales-preview/ipProtection.ftl @@ -80,6 +80,11 @@ ipprotection-toggle-active = ipprotection-toggle-inactive = .aria-label = Turn VPN on +# Button to turn off the VPN +ipprotection-button-turn-vpn-off = Turn off VPN +# Button to turn on the VPN +ipprotection-button-turn-vpn-on = Turn on VPN + ## Messages and errors ipprotection-message-generic-error =