tor-browser

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

commit 7a2d14d9d83167702545b9fab45c84c47a5f6484
parent 14c7e4f62496366a0daad35654714f49c8e5d016
Author: Meg Viar <lmegviar@gmail.com>
Date:   Tue, 14 Oct 2025 16:30:07 +0000

Bug 1993295 - Allow legacy telemetry upload for users who predate the new user TOU experience r=toolkit-telemetry-reviewers,dmose,TravisLong

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

Diffstat:
Mtoolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs | 59++++++++++++++++++++---------------------------------------
Mtoolkit/components/telemetry/tests/unit/head.js | 11+++++++++++
Mtoolkit/components/telemetry/tests/unit/test_PrefMigrationForTOU.js | 7+++++++
Mtoolkit/components/telemetry/tests/unit/test_TOUNotificationFlow.js | 285++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mtoolkit/crashreporter/test/unit/test_crashreporter_crash.js | 1+
5 files changed, 227 insertions(+), 136 deletions(-)

diff --git a/toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs b/toolkit/components/telemetry/app/TelemetryReportingPolicy.sys.mjs @@ -760,16 +760,9 @@ var TelemetryReportingPolicyImpl = { * Check if we are allowed to upload data. * Prerequisite: data submission is enabled (this.dataSubmissionEnabled). * - * In order to submit data, at least ONE of these conditions should be true: - * 1. The TOU flow is bypassed via a pref or Nimbus variable AND the legacy - * notification flow bypass pref is set, so users bypass BOTH flows. - * 2. The TOU flow is bypassed via a pref or Nimbus variable and the legacy - * notification flow bypass pref is NOT set, so has been been shown the - * legacy flow (the data submission pref should be true and the - * datachoices infobar should have been displayed). - * 3. The user has accepted the Terms of Use AND the user has opted-in to - * sharing technical interaction data (the upload enabled pref should be - * true). + * For upload to be allowed from a data reporting standpoint, the user should + * not qualify to see the legacy policy notification flow and also not qualify + * to see the Terms of Use acceptance flow. * @return {Boolean} True if we are allowed to upload data, false otherwise. */ canUpload() { @@ -779,33 +772,7 @@ var TelemetryReportingPolicyImpl = { return false; } - const bypassLegacyFlow = Services.prefs.getBoolPref( - TelemetryUtils.Preferences.BypassNotification, - false - ); - // TOU flow is bypassed if the Nimbus preonboarding feature is disabled - // (disabled by default for Linux via the fallback - // browser.preonboarding.enabled pref) or if the explicit bypass pref is - // set. - const bypassTOUFlow = - Services.prefs.getBoolPref(TOU_BYPASS_NOTIFICATION_PREF, false) || - (!Services.prefs.getBoolPref("browser.preonboarding.enabled", false) && - this._nimbusVariables?.enabled !== true) || - this._nimbusVariables?.enabled === false; - const allowInteractionData = Services.prefs.getBoolPref( - "datareporting.healthreport.uploadEnabled", - false - ); - - // Condition 1 - const canUploadBypassLegacyAndTOU = bypassLegacyFlow && bypassTOUFlow; - // Condition 2 - const canUploadLegacy = - bypassTOUFlow && !bypassLegacyFlow && this.isUserNotifiedOfCurrentPolicy; - // Condition 3 - const canUploadTOU = this.hasUserAcceptedCurrentTOU && allowInteractionData; - - return canUploadBypassLegacyAndTOU || canUploadLegacy || canUploadTOU; + return !this._shouldNotifyDataReportingPolicy() && !this._shouldShowTOU(); }, isFirstRun() { @@ -872,6 +839,20 @@ var TelemetryReportingPolicyImpl = { * Determine whether the user should be shown the terms of use. */ _shouldShowTOU() { + // In some cases, _shouldShowTOU can be called before the Nimbus variables + // are set. When this happens, we call _configureFromNimbus to initialize + // these variables before evaluating them. This ensures we have accurate + // data regarding whether preonboarding is enabled for the user. When + // preonboarding is explicitly disabled, it should be treated the same the + // bypassing the TOU flow via the bypass pref. + if ( + !this._nimbusVariables || + (typeof this._nimbusVariables === "object" && + Object.keys(this._nimbusVariables).length === 0) + ) { + this._configureFromNimbus(); + } + if (!this._nimbusVariables.enabled || !this._nimbusVariables.screens) { this._log.trace( "_shouldShowTOU - TOU not enabled or no screens configured." @@ -1096,7 +1077,7 @@ var TelemetryReportingPolicyImpl = { // _during_ the Firefox process lifetime; right now, we only notify the user // at Firefox startup. this.updateTOUPrefsForLegacyUsers(); - await this._configureFromNimbus(); + this._configureFromNimbus(); if (this.isFirstRun()) { // We're performing the first run, flip firstRun preference for subsequent runs. @@ -1233,7 +1214,7 @@ var TelemetryReportingPolicyImpl = { * Capture Nimbus configuration: record feature variables for future use and * set Gecko preferences based on values. */ - async _configureFromNimbus() { + _configureFromNimbus() { if (AppConstants.MOZ_BUILD_APP != "browser") { // OnboardingMessageProvider is browser/ only return; diff --git a/toolkit/components/telemetry/tests/unit/head.js b/toolkit/components/telemetry/tests/unit/head.js @@ -594,6 +594,17 @@ if (runningInParent) { ); } + // Disable TOU pre-onboarding in xpcshell so Telemetry isn't gated on Browser + // UI. + const TOS_ENABLED_PREF = "browser.preonboarding.enabled"; + const previous = Services.prefs.getBoolPref(TOS_ENABLED_PREF, false); + + Services.prefs.setBoolPref(TOS_ENABLED_PREF, false); + + registerCleanupFunction(() => { + Services.prefs.setBoolPref(TOS_ENABLED_PREF, previous); + }); + fakePingSendTimer( callback => { Services.tm.dispatchToMainThread(() => callback()); diff --git a/toolkit/components/telemetry/tests/unit/test_PrefMigrationForTOU.js b/toolkit/components/telemetry/tests/unit/test_PrefMigrationForTOU.js @@ -38,6 +38,13 @@ const skipIfNotBrowser = () => ({ skip_if: () => AppConstants.MOZ_BUILD_APP != "browser", }); +add_setup(() => { + // In head.js, we force TOU pre-onboarding off in xpcshell so Telemetry isn't + // gated on Browser UI. Revert for these tests. + const TOS_ENABLED_PREF = "browser.preonboarding.enabled"; + Services.prefs.clearUserPref(TOS_ENABLED_PREF); +}); + function setupLegacyAndRolloutPrefs({ acceptedVersion, notifiedTime, diff --git a/toolkit/components/telemetry/tests/unit/test_TOUNotificationFlow.js b/toolkit/components/telemetry/tests/unit/test_TOUNotificationFlow.js @@ -100,6 +100,34 @@ add_setup(skipIfNotBrowser(), async () => { registerCleanupFunction(cleanup); }); +add_setup(() => { + // In head.js, we force TOU pre-onboarding off in xpcshell so Telemetry isn't + // gated on Browser UI. Revert for these tests. + const TOS_ENABLED_PREF = "browser.preonboarding.enabled"; + Services.prefs.clearUserPref(TOS_ENABLED_PREF); +}); + +add_setup(() => { + // Clean up all potentially modified prefs and reset state after test tasks + // have run. + registerCleanupFunction(() => { + Services.prefs.clearUserPref(TOU_ACCEPTED_DATE_PREF); + Services.prefs.clearUserPref(TOU_ACCEPTED_VERSION_PREF); + Services.prefs.clearUserPref(TOU_BYPASS_NOTIFICATION_PREF); + Services.prefs.clearUserPref("browser.preonboarding.enabled"); + Services.prefs.clearUserPref("browser.preonboarding.screens"); + Services.prefs.clearUserPref( + "datareporting.policy.dataSubmissionPolicyNotifiedTime" + ); + Services.prefs.clearUserPref( + "datareporting.policy.dataSubmissionPolicyAcceptedVersion" + ); + Services.prefs.clearUserPref(TelemetryUtils.Preferences.BypassNotification); + + TelemetryReportingPolicy.reset(); + }); +}); + add_task(skipIfNotBrowser(), async function test_feature_prefs() { // Verify that feature values impact Gecko preferences at // `sessionstore-windows-restored` time, but not afterward. @@ -675,160 +703,223 @@ add_task( } ); -add_task( - async function test_canUpload_unblocked_by_tou_accepted_and_uploadEnabled() { - if (AppConstants.platform === "linux") { - info("Skipping test for Linux where TOU flow is disabled by default"); - return; - } - TelemetryReportingPolicy.reset(); +add_task(async function test_canUpload_unblocked_by_tou_accepted() { + if (AppConstants.platform === "linux") { + info("Skipping test for Linux where TOU flow is disabled by default"); + return; + } - registerCleanupFunction(() => { - Services.prefs.clearUserPref( - TelemetryUtils.Preferences.BypassNotification - ); - Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled"); - Services.prefs.clearUserPref("termsofuse.acceptedDate"); - Services.prefs.clearUserPref("termsofuse.acceptedVersion"); - TelemetryReportingPolicy.reset(); - }); + const cleanup = () => { + Services.prefs.clearUserPref(TelemetryUtils.Preferences.BypassNotification); + Services.prefs.clearUserPref("termsofuse.acceptedDate"); + Services.prefs.clearUserPref("termsofuse.acceptedVersion"); + TelemetryReportingPolicy.reset(); + }; - Services.prefs.setBoolPref( - TelemetryUtils.Preferences.BypassNotification, - false - ); + Services.prefs.setBoolPref( + TelemetryUtils.Preferences.BypassNotification, + false + ); - // This is true by default for most users - Services.prefs.setBoolPref( - "datareporting.healthreport.uploadEnabled", - true - ); + TelemetryReportingPolicy.reset(); - Assert.ok( - !TelemetryReportingPolicy.canUpload(), - "Before accepting TOU legacy upload is blocked" - ); + Assert.ok( + !TelemetryReportingPolicy.canUpload(), + "Before accepting TOU legacy upload is blocked" + ); - Assert.ok( - !Services.prefs.getIntPref( - "datareporting.policy.dataSubmissionPolicyAcceptedVersion", - 0 - ), - "Legacy policy flow accepted version not set" - ); + Assert.ok( + !Services.prefs.getIntPref( + "datareporting.policy.dataSubmissionPolicyAcceptedVersion", + 0 + ), + "Legacy policy flow accepted version not set" + ); - Assert.ok( - !Services.prefs.getBoolPref( - "datareporting.policy.dataSubmissionPolicyNotifiedDate", - 0 - ), - "Legacy policy flow accepted date not set" - ); + Assert.ok( + !Services.prefs.getBoolPref( + "datareporting.policy.dataSubmissionPolicyNotifiedTime", + 0 + ), + "Legacy policy flow accepted date not set" + ); - Services.prefs.setStringPref("termsofuse.acceptedDate", String(Date.now())); - Services.prefs.setIntPref( - "termsofuse.acceptedVersion", - Services.prefs.getIntPref(TOU_CURRENT_VERSION_PREF, 4) - ); + Services.prefs.setStringPref("termsofuse.acceptedDate", String(Date.now())); + Services.prefs.setIntPref( + "termsofuse.acceptedVersion", + Services.prefs.getIntPref(TOU_CURRENT_VERSION_PREF, 4) + ); - Assert.ok( - TelemetryReportingPolicy.userHasAcceptedTOU(), - "TOU acceptance is recorded" - ); + TelemetryReportingPolicy.reset(); - Assert.ok(TelemetryReportingPolicy.canUpload(), "Legacy upload allowed"); + Assert.ok( + TelemetryReportingPolicy.userHasAcceptedTOU(), + "TOU acceptance is recorded" + ); - Services.prefs.setBoolPref( - "datareporting.healthreport.uploadEnabled", - false - ); + Assert.ok(TelemetryReportingPolicy.canUpload(), "Legacy upload allowed"); - Assert.ok( - !TelemetryReportingPolicy.canUpload(), - "Legacy upload blocked when user opts out of sharing interaction data" - ); - } -); + cleanup(); +}); add_task(async function test_canUpload_allowed_when_both_bypass_prefs_true() { - TelemetryReportingPolicy.reset(); - - registerCleanupFunction(() => { + const cleanup = () => { Services.prefs.clearUserPref(TelemetryUtils.Preferences.BypassNotification); Services.prefs.clearUserPref("termsofuse.bypassNotification"); Services.prefs.clearUserPref("browser.preonboarding.enabled"); TelemetryReportingPolicy.reset(); - }); + }; Services.prefs.setBoolPref( TelemetryUtils.Preferences.BypassNotification, true ); Services.prefs.setBoolPref("termsofuse.bypassNotification", true); + Services.prefs.setBoolPref("browser.preonboarding.enabled", true); + TelemetryReportingPolicy.reset(); Assert.ok( TelemetryReportingPolicy.canUpload(), "Upload allowed when both legacy and TOU notification flows are bypassed" ); + Services.prefs.setBoolPref( + TelemetryUtils.Preferences.BypassNotification, + true + ); Services.prefs.setBoolPref("termsofuse.bypassNotification", false); - Services.prefs.setBoolPref("browser.preonboarding.enabled", true); + Services.prefs.setBoolPref("browser.preonboarding.screens", []); + + TelemetryReportingPolicy.reset(); Assert.ok( !TelemetryReportingPolicy.canUpload(), - "Upload blocked when only one bypass is true and no other allow path conditions are met" + "Upload not allowed when only legacy bypass is true and TOU should show" ); - Services.prefs.setBoolPref("browser.preonboarding.enabled", false); + + Services.prefs.setBoolPref( + TelemetryUtils.Preferences.BypassNotification, + false + ); + Services.prefs.setBoolPref("termsofuse.bypassNotification", true); + // Force TOU enabled so we’re not falling back to legacy notification (on + // Linux, this is false by default). + Services.prefs.setBoolPref("browser.preonboarding.enabled", true); + + TelemetryReportingPolicy.reset(); Assert.ok( - TelemetryReportingPolicy.canUpload(), - "Upload NOT blocked when only one bypass is true and no other allow path conditions are met" + !TelemetryReportingPolicy.canUpload(), + "Upload blocked when only TOU bypass is true and TOU should show" ); + + cleanup(); }); -add_task( - async function test_canUpload_allowed_when_tou_bypass_and_legacy_notified() { +add_task(async function test_canUpload_reconfigures_when_nimbus_not_ready() { + const cleanup = () => { + Services.prefs.clearUserPref("browser.preonboarding.enabled"); + Services.prefs.clearUserPref(TelemetryUtils.Preferences.BypassNotification); + Services.prefs.clearUserPref(TOU_BYPASS_NOTIFICATION_PREF); + Services.prefs.clearUserPref(TOU_ACCEPTED_VERSION_PREF); + Services.prefs.clearUserPref(TOU_ACCEPTED_DATE_PREF); + sinon.restore(); TelemetryReportingPolicy.reset(); + }; - registerCleanupFunction(() => { + // Force a path where canUpload() must consult Nimbus + Services.prefs.setBoolPref("browser.preonboarding.enabled", true); + Services.prefs.setBoolPref(TOU_BYPASS_NOTIFICATION_PREF, false); + Services.prefs.setBoolPref( + TelemetryUtils.Preferences.BypassNotification, + true + ); + + const getVarsSpy = sinon + .stub(NimbusFeatures.preonboarding, "getAllVariables") + .returns({ enabled: false }); + + TelemetryReportingPolicy.reset(); + + const result = TelemetryReportingPolicy.canUpload(); + + Assert.equal( + getVarsSpy.callCount, + 1, + "canUpload() should trigger _configureFromNimbus() when variables are not yet set" + ); + + Assert.ok( + result, + "With Nimbus enabled=false, TOU is off; legacy bypass allows upload" + ); + + cleanup(); +}); + +add_task( + skipIfNotBrowser(), + async function test_legacy_bypass_blocked_when_TOU_should_show_and_not_accepted() { + const cleanup = async () => { + Services.prefs.clearUserPref("browser.preonboarding.enabled"); Services.prefs.clearUserPref( TelemetryUtils.Preferences.BypassNotification ); - Services.prefs.clearUserPref("termsofuse.bypassNotification"); + Services.prefs.clearUserPref(TOU_BYPASS_NOTIFICATION_PREF); + Services.prefs.clearUserPref(TOU_ACCEPTED_VERSION_PREF); + Services.prefs.clearUserPref(TOU_ACCEPTED_DATE_PREF); Services.prefs.clearUserPref( - "datareporting.policy.dataSubmissionPolicyNotifiedDate" + "datareporting.policy.dataSubmissionPolicyNotifiedTime" ); Services.prefs.clearUserPref( - "TelemetryUtils.Preferences.DataSubmissionEnabled" + "datareporting.policy.dataSubmissionPolicyAcceptedVersion" ); TelemetryReportingPolicy.reset(); - }); + await unenroll(); + }; - Services.prefs.setBoolPref("termsofuse.bypassNotification", true); + // Legacy bypass ON, TOU bypass OFF Services.prefs.setBoolPref( TelemetryUtils.Preferences.BypassNotification, - false - ); - Services.prefs.setBoolPref( - TelemetryUtils.Preferences.DataSubmissionEnabled, true ); + Services.prefs.setBoolPref(TOU_BYPASS_NOTIFICATION_PREF, false); + + // User has NOT accepted TOU + Services.prefs.clearUserPref(TOU_ACCEPTED_VERSION_PREF); + Services.prefs.clearUserPref(TOU_ACCEPTED_DATE_PREF); + + // User has NOT accepted via Legacy flow + Services.prefs.clearUserPref( + "datareporting.policy.dataSubmissionPolicyNotifiedTime" + ); + Services.prefs.clearUserPref( + "datareporting.policy.dataSubmissionPolicyAcceptedVersion" + ); + + // Make _shouldShowTOU() return true + Services.prefs.setBoolPref("browser.preonboarding.enabled", true); + const unenroll = await NimbusTestUtils.enrollWithFeatureConfig( + { + featureId: NimbusFeatures.preonboarding.featureId, + value: { + enabled: true, + currentVersion: 4, + minimumVersion: 4, + screens: [{ id: "test" }], + }, + }, + { isRollout: false } + ); - Assert.ok( - !TelemetryReportingPolicy.canUpload(), - "Upload not allowed when TOU bypass is true, legacy bypass is false, and legacy policy has NOT been notified" - ); - // Mark legacy “notified” via the actual code path triggered via - // testInfobarShown. This calls _recordNotificationData, which updates the - // legacy notification accepted time and version prefs. It also calls - // _userNotified which in turn calls lazy.TelemetrySend.notifyCanUpload. - // This simulates the user being notified of the legacy infobar flow. - TelemetryReportingPolicy.testInfobarShown(); - // Ensure the policy re-reads state after pref mutations. TelemetryReportingPolicy.reset(); + const result = TelemetryReportingPolicy.canUpload(); + Assert.ok( - TelemetryReportingPolicy.canUpload(), - "Upload allowed when TOU bypass is true, legacy bypass is false, and legacy policy has been notified" + !result, + "When TOU flow should be shown, but is not yet accepted with legacy bypass true, (!shouldShowTOU && bypassLegacy) is false, so upload is blocked." ); + + await cleanup(); } ); diff --git a/toolkit/crashreporter/test/unit/test_crashreporter_crash.js b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js @@ -113,6 +113,7 @@ add_task(async function run_test() { // Enable the FHR, official policy bypass (since we're in a test) and // specify a telemetry server & client ID. Services.prefs.setBoolPref("termsofuse.bypassNotification", true); + Services.prefs.setBoolPref("browser.preonboarding.enabled", false); Services.prefs.setBoolPref( "datareporting.policy.dataSubmissionPolicyBypassNotification", true