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:
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 =