tor-browser

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

commit 83187fd2a59fcba6c8ca96fc05a1cb73a5a4ad4d
parent 174d199de0f6af71b9b63944e8723e0d5a6805b4
Author: Chris H-C <chutten@mozilla.com>
Date:   Mon,  1 Dec 2025 21:43:53 +0000

Bug 1999541 - Implement a Legacy Telemetry shutoff switch for pings controlled by Nimbus r=TravisLong

We preserve "main", "first-shutdown", "new-profile", and "deletion-request" pings
from being disable-able by this method to prevent unintended effects on KPIs or
our ability to self-serve data deletion.

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

Diffstat:
Mtoolkit/components/nimbus/FeatureManifest.yaml | 17+++++++++++++++++
Mtoolkit/components/telemetry/app/TelemetryControllerParent.sys.mjs | 22++++++++++++++++++++++
Mtoolkit/components/telemetry/tests/unit/test_TelemetryController.js | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 144 insertions(+), 0 deletions(-)

diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml @@ -2725,6 +2725,23 @@ gleanInternalSdk: branch: user pref: telemetry.glean.internal.maxPingsPerMinute +legacyTelemetry: + description: "Controls for the Legacy Telemetry data collection system" + owner: chutten@mozilla.com + applications: + - firefox-desktop + hasExposure: false + variables: + disabledPings: + type: json + description: | + A list of Legacy Telemetry pings to disable. + Pings on this list will not be archived or uploaded. + On submit their payloads will be dropped. + Code that collects to and submits the ping will still operate as normal. + Cannot be used to disable the "main", "first-shutdown", "new-profile", + or "deletion-request" pings. + browserLowMemoryPrefs: description: Prefs which control the browser's behaviour under low memory. owner: haftandilian@mozilla.com diff --git a/toolkit/components/telemetry/app/TelemetryControllerParent.sys.mjs b/toolkit/components/telemetry/app/TelemetryControllerParent.sys.mjs @@ -38,6 +38,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { ClientID: "resource://gre/modules/ClientID.sys.mjs", CoveragePing: "resource://gre/modules/CoveragePing.sys.mjs", + NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", TelemetryArchive: "resource://gre/modules/TelemetryArchive.sys.mjs", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", TelemetryEventPing: "resource://gre/modules/EventPing.sys.mjs", @@ -528,6 +529,27 @@ var Impl = { JSON.stringify(aOptions) ); + const disabledPings = + lazy.NimbusFeatures.legacyTelemetry.getVariable("disabledPings") ?? []; + const UNCONTROLLABLE_PINGS = [ + "main", + "first-shutdown", + "new-profile", + "deletion-request", + ]; + if (disabledPings.includes(aType)) { + if (UNCONTROLLABLE_PINGS.includes(aType)) { + this._log.warn( + `submitExternalPing - type: ${aType} not controllable, but is in the list of disabledPings ${JSON.stringify(disabledPings)}. Ping will submit as normal. Please remove ping type "${aType}" from the Nimbus config.` + ); + } else { + this._log.trace( + `submitExternalPing - type ${aType} disabled by Nimbus.` + ); + return Promise.reject(new Error("Ping disabled.")); + } + } + // Reject pings sent after shutdown. if (this._shutDown) { const errorMessage = diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js @@ -11,6 +11,12 @@ const { ClientID } = ChromeUtils.importESModule( "resource://gre/modules/ClientID.sys.mjs" ); +const { ExperimentAPI, NimbusFeatures } = ChromeUtils.importESModule( + "resource://nimbus/ExperimentAPI.sys.mjs" +); +const { NimbusTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/NimbusTestUtils.sys.mjs" +); const { TelemetryController } = ChromeUtils.importESModule( "resource://gre/modules/TelemetryController.sys.mjs" ); @@ -33,6 +39,8 @@ const { TestUtils } = ChromeUtils.importESModule( "resource://testing-common/TestUtils.sys.mjs" ); +NimbusTestUtils.init(this); + const PING_FORMAT_VERSION = 4; const DELETION_REQUEST_PING_TYPE = "deletion-request"; const TEST_PING_TYPE = "test-ping-type"; @@ -897,6 +905,103 @@ add_task(async function test_sendNewProfile() { PingServer.resetPingHandler(); }); +add_task(async function test_pingDisablement() { + await TelemetryController.testReset(); + PingServer.clearRequests(); + + info("1. Ensure test pings can be sent."); + let docid = await sendPing(false, false); + let request = await PingServer.promiseNextRequest(); + + let ping = decodeRequestPayload(request); + Assert.equal(docid, ping.id, "Server delivered the ping we just submitted."); + checkPingFormat(ping, TEST_PING_TYPE, false, false); + + info("2. Ensure we can disable a ping by name."); + const { cleanup } = await NimbusTestUtils.setupTest(); + registerCleanupFunction(cleanup); + await ExperimentAPI.ready(); + let nimbusCleanup = await NimbusTestUtils.enrollWithFeatureConfig({ + featureId: NimbusFeatures.legacyTelemetry.featureId, + value: { + disabledPings: [TEST_PING_TYPE], + }, + }); + + PingServer.registerPingHandler(() => + Assert.ok(false, "Telemetry must not send the disabled ping.") + ); + await Assert.rejects( + sendPing(true, true), + /Ping disabled/, + "Disabled ping should not send." + ); + + PingServer.resetPingHandler(); + + info("3. Ensure disabling one kind of ping doesn't disable others."); + const OTHER_PING_TYPE = TEST_PING_TYPE + "-other"; + await TelemetryController.submitExternalPing(OTHER_PING_TYPE, {}, {}); + request = await PingServer.promiseNextRequest(); + + ping = decodeRequestPayload(request); + checkPingFormat(ping, OTHER_PING_TYPE, false, false); + + await nimbusCleanup(); +}); + +add_task(async function test_cantDisableImportantPings() { + await TelemetryController.testReset(); + PingServer.clearRequests(); + + const DO_NOT_DISABLE_THESE_PINGS = [ + "main", + "first-shutdown", + "new-profile", + "deletion-request", + ]; + const PINGS = [TEST_PING_TYPE, ...DO_NOT_DISABLE_THESE_PINGS]; + let nimbusCleanup = await NimbusTestUtils.enrollWithFeatureConfig({ + featureId: NimbusFeatures.legacyTelemetry.featureId, + value: { + disabledPings: PINGS, + }, + }); + + for (const pingName of PINGS) { + info("Check " + pingName); + if (DO_NOT_DISABLE_THESE_PINGS.includes(pingName)) { + let docid = await TelemetryController.submitExternalPing( + pingName, + {}, + {} + ); + let request = await PingServer.promiseNextRequest(); + let ping = decodeRequestPayload(request); + Assert.equal( + docid, + ping.id, + "Server delivered the ping we just submitted." + ); + checkPingFormat(ping, pingName, false, false); + // Ensure we don't get throttled for too many pings in a row. + await TelemetrySend.reset(); + } else { + PingServer.registerPingHandler(() => + Assert.ok(false, "Telemetry must not send the disabled ping.") + ); + await Assert.rejects( + TelemetryController.submitExternalPing(pingName, {}, {}), + /Ping disabled/, + "Disabled ping should not send." + ); + PingServer.resetPingHandler(); + } + } + + await nimbusCleanup(); +}); + // Testing shutdown and checking that pings sent afterwards are rejected. add_task(async function test_pingRejection() { await TelemetryController.testReset();