diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /browser/components/doh/test/browser | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/doh/test/browser')
15 files changed, 1606 insertions, 0 deletions
diff --git a/browser/components/doh/test/browser/browser.ini b/browser/components/doh/test/browser/browser.ini new file mode 100644 index 0000000000..d440c2f393 --- /dev/null +++ b/browser/components/doh/test/browser/browser.ini @@ -0,0 +1,21 @@ +[DEFAULT] +head = head.js + +[browser_cleanFlow.js] +skip-if = socketprocess_networking +[browser_dirtyEnable.js] +[browser_doorhangerUserReject.js] +[browser_platformDetection.js] +[browser_policyOverride.js] +[browser_providerSteering.js] +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_remoteSettings_newProfile.js] +skip-if = os == 'win' && bits == 32 # Bug 1713464 +[browser_remoteSettings_rollout.js] +skip-if = os == 'win' && bits == 32 # Bug 1713464 +[browser_rollback.js] +[browser_throttle_heuristics.js] +[browser_trrSelect.js] +[browser_trrSelection_disable.js] +[browser_userInterference.js] diff --git a/browser/components/doh/test/browser/browser_cleanFlow.js b/browser/components/doh/test/browser/browser_cleanFlow.js new file mode 100644 index 0000000000..9beb1a5a26 --- /dev/null +++ b/browser/components/doh/test/browser/browser_cleanFlow.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +add_task(async function testCleanFlow() { + // Set up a passing environment and enable DoH. + setPassingHeuristics(); + let promise = waitForDoorhanger(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete." + ); + await checkTRRSelectionTelemetry(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EXAMPLE_URL); + let panel = await promise; + + prefPromise = TestUtils.waitForPrefChange( + prefs.DOORHANGER_USER_DECISION_PREF + ); + + // Click the doorhanger's "accept" button. + let button = panel.querySelector(".popup-notification-primary-button"); + promise = BrowserTestUtils.waitForEvent(panel, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(button, {}); + await promise; + + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + await prefPromise; + is( + Preferences.get(prefs.DOORHANGER_USER_DECISION_PREF), + "UIOk", + "Doorhanger decision saved." + ); + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb not cleared."); + + BrowserTestUtils.removeTab(tab); + + // Change the environment to failing and simulate a network change. + setFailingHeuristics(); + simulateNetworkChange(); + await ensureTRRMode(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + // Trigger another network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + // Restart the controller for good measure. + await restartDoHController(); + ensureNoTRRSelectionTelemetry(); + // The mode technically changes from undefined/empty to 0 here. + await ensureTRRMode(0); + await checkHeuristicsTelemetry("disable_doh", "startup"); + + // Set a passing environment and simulate a network change. + setPassingHeuristics(); + simulateNetworkChange(); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "netchange"); + + // Again, repeat and check nothing changed. + simulateNetworkChange(); + await ensureNoTRRModeChange(2); + await checkHeuristicsTelemetry("enable_doh", "netchange"); + + // Test the clearModeOnShutdown pref. `restartDoHController` does the actual + // test for us between shutdown and startup. + Preferences.set(prefs.CLEAR_ON_SHUTDOWN_PREF, false); + await restartDoHController(); + ensureNoTRRSelectionTelemetry(); + await ensureNoTRRModeChange(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + Preferences.set(prefs.CLEAR_ON_SHUTDOWN_PREF, true); +}); diff --git a/browser/components/doh/test/browser/browser_dirtyEnable.js b/browser/components/doh/test/browser/browser_dirtyEnable.js new file mode 100644 index 0000000000..c704ca06e6 --- /dev/null +++ b/browser/components/doh/test/browser/browser_dirtyEnable.js @@ -0,0 +1,55 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +add_task(async function testDirtyEnable() { + // Set up a failing environment, pre-set DoH to enabled, and verify that + // when the add-on is enabled, it doesn't do anything - DoH remains turned on. + setFailingHeuristics(); + let prefPromise = TestUtils.waitForPrefChange(prefs.DISABLED_PREF); + Preferences.set(prefs.NETWORK_TRR_MODE_PREF, 2); + Preferences.set(prefs.ENABLED_PREF, true); + await prefPromise; + is( + Preferences.get(prefs.DISABLED_PREF, false), + true, + "Disabled state recorded." + ); + is( + Preferences.get(prefs.BREADCRUMB_PREF), + undefined, + "Breadcrumb not saved." + ); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + undefined, + "TRR selection not performed." + ); + is(Preferences.get(prefs.NETWORK_TRR_MODE_PREF), 2, "TRR mode preserved."); + ensureNoTRRSelectionTelemetry(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + // Simulate a network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + is(Preferences.get(prefs.NETWORK_TRR_MODE_PREF), 2, "TRR mode preserved."); + + // Restart the controller for good measure. + await restartDoHController(); + await ensureNoTRRModeChange(undefined); + ensureNoTRRSelectionTelemetry(); + ensureNoHeuristicsTelemetry(); + is(Preferences.get(prefs.NETWORK_TRR_MODE_PREF), 2, "TRR mode preserved."); + + // Simulate a network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + is(Preferences.get(prefs.NETWORK_TRR_MODE_PREF), 2, "TRR mode preserved."); + ensureNoHeuristicsTelemetry(); +}); diff --git a/browser/components/doh/test/browser/browser_doorhangerUserReject.js b/browser/components/doh/test/browser/browser_doorhangerUserReject.js new file mode 100644 index 0000000000..627ca48db2 --- /dev/null +++ b/browser/components/doh/test/browser/browser_doorhangerUserReject.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +add_task(async function testDoorhangerUserReject() { + // Set up a passing environment and enable DoH. + setPassingHeuristics(); + let promise = waitForDoorhanger(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete." + ); + await checkTRRSelectionTelemetry(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EXAMPLE_URL); + let panel = await promise; + + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + prefPromise = TestUtils.waitForPrefChange( + prefs.DOORHANGER_USER_DECISION_PREF + ); + + // Click the doorhanger's "reject" button. + let button = panel.querySelector(".popup-notification-secondary-button"); + promise = BrowserTestUtils.waitForEvent(panel, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(button, {}); + await promise; + + await prefPromise; + + is( + Preferences.get(prefs.DOORHANGER_USER_DECISION_PREF), + "UIDisabled", + "Doorhanger decision saved." + ); + + BrowserTestUtils.removeTab(tab); + + await ensureTRRMode(undefined); + ensureNoHeuristicsTelemetry(); + is(Preferences.get(prefs.BREADCRUMB_PREF), undefined, "Breadcrumb cleared."); + + // Simulate a network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + // Restart the controller for good measure. + await restartDoHController(); + ensureNoTRRSelectionTelemetry(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + // Set failing environment and trigger another network change. + setFailingHeuristics(); + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); +}); diff --git a/browser/components/doh/test/browser/browser_platformDetection.js b/browser/components/doh/test/browser/browser_platformDetection.js new file mode 100644 index 0000000000..bf97cab196 --- /dev/null +++ b/browser/components/doh/test/browser/browser_platformDetection.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + Heuristics: "resource:///modules/DoHHeuristics.sys.mjs", +}); + +add_task(setup); + +add_task(async function testPlatformIndications() { + // Check if the platform heuristics actually cause a "disable_doh" event + + let { MockRegistrar } = ChromeUtils.importESModule( + "resource://testing-common/MockRegistrar.sys.mjs" + ); + + let mockedLinkService = { + isLinkUp: true, + linkStatusKnown: true, + linkType: Ci.nsINetworkLinkService.LINK_TYPE_WIFI, + networkID: "abcd", + dnsSuffixList: [], + platformDNSIndications: Ci.nsINetworkLinkService.NONE_DETECTED, + QueryInterface: ChromeUtils.generateQI(["nsINetworkLinkService"]), + }; + + let networkLinkServiceCID = MockRegistrar.register( + "@mozilla.org/network/network-link-service;1", + mockedLinkService + ); + + Heuristics._setMockLinkService(mockedLinkService); + registerCleanupFunction(async () => { + MockRegistrar.unregister(networkLinkServiceCID); + Heuristics._setMockLinkService(undefined); + }); + + setPassingHeuristics(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + await ensureTRRMode(2); + + mockedLinkService.platformDNSIndications = + Ci.nsINetworkLinkService.VPN_DETECTED; + simulateNetworkChange(); + await ensureTRRMode(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + mockedLinkService.platformDNSIndications = + Ci.nsINetworkLinkService.PROXY_DETECTED; + simulateNetworkChange(); + await ensureNoTRRModeChange(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + mockedLinkService.platformDNSIndications = + Ci.nsINetworkLinkService.NRPT_DETECTED; + simulateNetworkChange(); + await ensureNoTRRModeChange(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + mockedLinkService.platformDNSIndications = + Ci.nsINetworkLinkService.NONE_DETECTED; + simulateNetworkChange(); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "netchange"); +}); diff --git a/browser/components/doh/test/browser/browser_policyOverride.js b/browser/components/doh/test/browser/browser_policyOverride.js new file mode 100644 index 0000000000..25fd2c037e --- /dev/null +++ b/browser/components/doh/test/browser/browser_policyOverride.js @@ -0,0 +1,66 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +const { EnterprisePolicyTesting } = ChromeUtils.importESModule( + "resource://testing-common/EnterprisePolicyTesting.sys.mjs" +); + +add_task(async function testPolicyOverride() { + // Set up an arbitrary enterprise policy. Its existence should be sufficient + // to disable heuristics. + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: { + EnableTrackingProtection: { + Value: true, + }, + }, + }); + is( + Services.policies.status, + Ci.nsIEnterprisePolicies.ACTIVE, + "Policy engine is active." + ); + + Preferences.set(prefs.ENABLED_PREF, true); + await waitForStateTelemetry(["shutdown", "policyDisabled"]); + is( + Preferences.get(prefs.BREADCRUMB_PREF), + undefined, + "Breadcrumb not saved." + ); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + undefined, + "TRR selection not performed." + ); + is( + Preferences.get(prefs.SKIP_HEURISTICS_PREF), + true, + "Pref set to suppress CFR." + ); + ensureNoTRRSelectionTelemetry(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + // Simulate a network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + // Clean up. + await EnterprisePolicyTesting.setupPolicyEngineWithJson({ + policies: {}, + }); + EnterprisePolicyTesting.resetRunOnceState(); + + is( + Services.policies.status, + Ci.nsIEnterprisePolicies.INACTIVE, + "Policy engine is inactive at the end of the test" + ); +}); diff --git a/browser/components/doh/test/browser/browser_providerSteering.js b/browser/components/doh/test/browser/browser_providerSteering.js new file mode 100644 index 0000000000..069a823a07 --- /dev/null +++ b/browser/components/doh/test/browser/browser_providerSteering.js @@ -0,0 +1,107 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const TEST_DOMAIN = "doh.test."; +const AUTO_TRR_URI = "https://example.com/dns-query"; + +add_task(setup); + +add_task(async function testProviderSteering() { + setPassingHeuristics(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + let providerTestcases = [ + { + id: "provider1", + canonicalName: "foo.provider1.com", + uri: "https://foo.provider1.com/query", + }, + { + id: "provider2", + canonicalName: "bar.provider2.com", + uri: "https://bar.provider2.com/query", + }, + ]; + let configFlushPromise = DoHTestUtils.waitForConfigFlush(); + Preferences.set( + prefs.PROVIDER_STEERING_LIST_PREF, + JSON.stringify(providerTestcases) + ); + await configFlushPromise; + await checkHeuristicsTelemetry("enable_doh", "startup"); + + let testNetChangeResult = async ( + expectedURI, + heuristicsDecision, + providerName + ) => { + let trrURIChanged = TestUtils.topicObserved( + "network:trr-uri-changed", + () => { + // We need this check because this topic is observed once immediately + // after the network change when the URI is reset, and then when the + // provider steering heuristic runs and sets it to our uri. + return Services.dns.currentTrrURI == expectedURI; + } + ); + simulateNetworkChange(); + await trrURIChanged; + is( + Services.dns.currentTrrURI, + expectedURI, + `TRR URI set to ${expectedURI}` + ); + await checkHeuristicsTelemetry( + heuristicsDecision, + "netchange", + providerName + ); + }; + + for (let { id, canonicalName, uri } of providerTestcases) { + gDNSOverride.addIPOverride(TEST_DOMAIN, "9.9.9.9"); + gDNSOverride.setCnameOverride(TEST_DOMAIN, canonicalName); + await testNetChangeResult(uri, "enable_doh", id); + gDNSOverride.clearHostOverride(TEST_DOMAIN); + } + + await testNetChangeResult(AUTO_TRR_URI, "enable_doh"); + + // Just use the first provider for the remaining checks. + let provider = providerTestcases[0]; + gDNSOverride.addIPOverride(TEST_DOMAIN, "9.9.9.9"); + gDNSOverride.setCnameOverride(TEST_DOMAIN, provider.canonicalName); + await testNetChangeResult(provider.uri, "enable_doh", provider.id); + + // Set enterprise roots enabled and ensure provider steering is disabled. + Preferences.set("security.enterprise_roots.enabled", true); + await testNetChangeResult(AUTO_TRR_URI, "disable_doh"); + Preferences.reset("security.enterprise_roots.enabled"); + + // Check that provider steering is enabled again after we reset above. + await testNetChangeResult(provider.uri, "enable_doh", provider.id); + + // Trigger safesearch heuristics and ensure provider steering is disabled. + let googleDomain = "google.com."; + let googleIP = "1.1.1.1"; + let googleSafeSearchIP = "1.1.1.2"; + gDNSOverride.clearHostOverride(googleDomain); + gDNSOverride.addIPOverride(googleDomain, googleSafeSearchIP); + await testNetChangeResult(AUTO_TRR_URI, "disable_doh"); + gDNSOverride.clearHostOverride(googleDomain); + gDNSOverride.addIPOverride(googleDomain, googleIP); + + // Check that provider steering is enabled again after we reset above. + await testNetChangeResult(provider.uri, "enable_doh", provider.id); + + // Finally, provider steering should be disabled once we clear the override. + gDNSOverride.clearHostOverride(TEST_DOMAIN); + await testNetChangeResult(AUTO_TRR_URI, "enable_doh"); +}); diff --git a/browser/components/doh/test/browser/browser_remoteSettings_newProfile.js b/browser/components/doh/test/browser/browser_remoteSettings_newProfile.js new file mode 100644 index 0000000000..cd4356ed3f --- /dev/null +++ b/browser/components/doh/test/browser/browser_remoteSettings_newProfile.js @@ -0,0 +1,147 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); +add_task(setupRegion); + +async function setPrefAndWaitForConfigFlush(pref, value) { + let configFlushedPromise = DoHTestUtils.waitForConfigFlush(); + Preferences.set(pref, value); + await configFlushedPromise; +} + +async function clearPrefAndWaitForConfigFlush(pref, value) { + let configFlushedPromise = DoHTestUtils.waitForConfigFlush(); + Preferences.reset(pref); + await configFlushedPromise; +} + +add_task(async function testNewProfile() { + is( + DoHConfigController.currentConfig.enabled, + false, + "Rollout should not be enabled" + ); + + let provider1 = { + id: "provider1", + uri: "https://example.org/1", + autoDefault: true, + }; + let provider2 = { + id: "provider2", + uri: "https://example.org/2", + canonicalName: "https://example.org/cname", + }; + let provider3 = { + id: "provider3", + uri: "https://example.org/3", + autoDefault: true, + }; + + await DoHTestUtils.loadRemoteSettingsProviders([ + provider1, + provider2, + provider3, + ]); + + await DoHTestUtils.loadRemoteSettingsConfig({ + id: kTestRegion.toLowerCase(), + rolloutEnabled: true, + providers: "provider1, provider3", + steeringEnabled: true, + steeringProviders: "provider2", + autoDefaultEnabled: true, + autoDefaultProviders: "provider1, provider3", + }); + + is( + DoHConfigController.currentConfig.enabled, + true, + "Rollout should be enabled" + ); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + Assert.deepEqual( + DoHConfigController.currentConfig.providerList, + [provider1, provider3], + "Provider list should be loaded" + ); + is( + DoHConfigController.currentConfig.providerSteering.enabled, + true, + "Steering should be enabled" + ); + Assert.deepEqual( + DoHConfigController.currentConfig.providerSteering.providerList, + [provider2], + "Steering provider list should be loaded" + ); + is( + DoHConfigController.currentConfig.trrSelection.enabled, + true, + "TRR Selection should be enabled" + ); + Assert.deepEqual( + DoHConfigController.currentConfig.trrSelection.providerList, + [provider1, provider3], + "TRR Selection provider list should be loaded" + ); + is( + DoHConfigController.currentConfig.fallbackProviderURI, + provider1.uri, + "Fallback provider URI should be that of the first one" + ); + + // Test that overriding with prefs works. + await setPrefAndWaitForConfigFlush(prefs.PROVIDER_STEERING_PREF, false); + is( + DoHConfigController.currentConfig.providerSteering.enabled, + false, + "Provider steering should be disabled" + ); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + await setPrefAndWaitForConfigFlush(prefs.TRR_SELECT_ENABLED_PREF, false); + is( + DoHConfigController.currentConfig.trrSelection.enabled, + false, + "TRR selection should be disabled" + ); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + // Try a regional pref this time + await setPrefAndWaitForConfigFlush( + `${kRegionalPrefNamespace}.enabled`, + false + ); + is( + DoHConfigController.currentConfig.enabled, + false, + "Rollout should be disabled" + ); + await ensureTRRMode(undefined); + await ensureNoHeuristicsTelemetry(); + + await clearPrefAndWaitForConfigFlush(`${kRegionalPrefNamespace}.enabled`); + + is( + DoHConfigController.currentConfig.enabled, + true, + "Rollout should be enabled" + ); + + await DoHTestUtils.resetRemoteSettingsConfig(); + + is( + DoHConfigController.currentConfig.enabled, + false, + "Rollout should be disabled" + ); + await ensureTRRMode(undefined); +}); diff --git a/browser/components/doh/test/browser/browser_remoteSettings_rollout.js b/browser/components/doh/test/browser/browser_remoteSettings_rollout.js new file mode 100644 index 0000000000..e0e31dd238 --- /dev/null +++ b/browser/components/doh/test/browser/browser_remoteSettings_rollout.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); +add_task(setupRegion); + +add_task(async function testPrefFirstRollout() { + let defaults = Services.prefs.getDefaultBranch(""); + + setPassingHeuristics(); + + is( + DoHConfigController.currentConfig.enabled, + false, + "Rollout should not be enabled" + ); + + let configFlushedPromise = DoHTestUtils.waitForConfigFlush(); + defaults.setBoolPref(`${kRegionalPrefNamespace}.enabled`, true); + await configFlushedPromise; + + is( + DoHConfigController.currentConfig.enabled, + true, + "Rollout should be enabled" + ); + await ensureTRRMode(2); + + await DoHTestUtils.loadRemoteSettingsProviders([ + { + id: "provider1", + uri: "https://example.org/1", + autoDefault: true, + }, + ]); + + await DoHTestUtils.loadRemoteSettingsConfig({ + id: kTestRegion.toLowerCase(), + rolloutEnabled: true, + providers: "provider1", + }); + + is( + DoHConfigController.currentConfig.enabled, + true, + "Rollout should still be enabled" + ); + + defaults.deleteBranch(`${kRegionalPrefNamespace}.enabled`); + await restartDoHController(); + + is( + DoHConfigController.currentConfig.enabled, + true, + "Rollout should still be enabled" + ); + await ensureTRRMode(2); + + await DoHTestUtils.resetRemoteSettingsConfig(); + + is( + DoHConfigController.currentConfig.enabled, + false, + "Rollout should not be enabled" + ); + await ensureTRRMode(undefined); +}); diff --git a/browser/components/doh/test/browser/browser_rollback.js b/browser/components/doh/test/browser/browser_rollback.js new file mode 100644 index 0000000000..b414c35ae5 --- /dev/null +++ b/browser/components/doh/test/browser/browser_rollback.js @@ -0,0 +1,144 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +requestLongerTimeout(2); + +add_task(setup); + +add_task(async function testRollback() { + // Set up a passing environment and enable DoH. + setPassingHeuristics(); + let promise = waitForDoorhanger(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete." + ); + await checkTRRSelectionTelemetry(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EXAMPLE_URL); + let panel = await promise; + + prefPromise = TestUtils.waitForPrefChange( + prefs.DOORHANGER_USER_DECISION_PREF + ); + + // Click the doorhanger's "accept" button. + let button = panel.querySelector(".popup-notification-primary-button"); + promise = BrowserTestUtils.waitForEvent(panel, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(button, {}); + await promise; + + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + await prefPromise; + is( + Preferences.get(prefs.DOORHANGER_USER_DECISION_PREF), + "UIOk", + "Doorhanger decision saved." + ); + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb not cleared."); + + BrowserTestUtils.removeTab(tab); + + // Change the environment to failing and simulate a network change. + setFailingHeuristics(); + simulateNetworkChange(); + await ensureTRRMode(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + // Trigger another network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + // Rollback! + setPassingHeuristics(); + Preferences.reset(prefs.ENABLED_PREF); + await waitForStateTelemetry(["shutdown", "rollback"]); + await ensureTRRMode(undefined); + ensureNoTRRSelectionTelemetry(); + await ensureNoHeuristicsTelemetry(); + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + await ensureNoHeuristicsTelemetry(); + + // Re-enable. + Preferences.set(prefs.ENABLED_PREF, true); + + await ensureTRRMode(2); + ensureNoTRRSelectionTelemetry(); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + // Change the environment to failing and simulate a network change. + setFailingHeuristics(); + simulateNetworkChange(); + await ensureTRRMode(0); + await checkHeuristicsTelemetry("disable_doh", "netchange"); + + // Rollback again for good measure! This time with failing heuristics. + Preferences.reset(prefs.ENABLED_PREF); + await waitForStateTelemetry(["shutdown", "rollback"]); + await ensureTRRMode(undefined); + ensureNoTRRSelectionTelemetry(); + await ensureNoHeuristicsTelemetry(); + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + await ensureNoHeuristicsTelemetry(); + + // Re-enable. + Preferences.set(prefs.ENABLED_PREF, true); + + await ensureTRRMode(0); + ensureNoTRRSelectionTelemetry(); + await checkHeuristicsTelemetry("disable_doh", "startup"); + + // Change the environment to passing and simulate a network change. + setPassingHeuristics(); + simulateNetworkChange(); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "netchange"); + + // Rollback again, this time with TRR mode set to 2 prior to doing so. + Preferences.reset(prefs.ENABLED_PREF); + await waitForStateTelemetry(["shutdown", "rollback"]); + await ensureTRRMode(undefined); + ensureNoTRRSelectionTelemetry(); + await ensureNoHeuristicsTelemetry(); + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + await ensureNoHeuristicsTelemetry(); + + // Re-enable. + Preferences.set(prefs.ENABLED_PREF, true); + + await ensureTRRMode(2); + ensureNoTRRSelectionTelemetry(); + await checkHeuristicsTelemetry("enable_doh", "startup"); + simulateNetworkChange(); + await ensureNoTRRModeChange(2); + await checkHeuristicsTelemetry("enable_doh", "netchange"); + + // Rollback again. This time, uninit DoHController first to ensure it reacts + // correctly at startup. + await DoHController._uninit(); + await waitForStateTelemetry(["shutdown"]); + Preferences.reset(prefs.ENABLED_PREF); + await DoHController.init(); + await ensureTRRMode(undefined); + ensureNoTRRSelectionTelemetry(); + await ensureNoHeuristicsTelemetry(); + await waitForStateTelemetry(["rollback"]); + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + await ensureNoHeuristicsTelemetry(); +}); diff --git a/browser/components/doh/test/browser/browser_throttle_heuristics.js b/browser/components/doh/test/browser/browser_throttle_heuristics.js new file mode 100644 index 0000000000..7a0b22ed11 --- /dev/null +++ b/browser/components/doh/test/browser/browser_throttle_heuristics.js @@ -0,0 +1,97 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +add_task(async function testHeuristicsThrottling() { + // Use a zero throttle timeout for the test. This both ensures the test has a + // short runtime as well as preventing intermittents because we thought + // something was deterministic when it wasn't. + let throttleTimeout = 0; + let rateLimit = 1; + let throttleDoneTopic = "doh:heuristics-throttle-done"; + let throttleExtendTopic = "doh:heuristics-throttle-extend"; + + Preferences.set(prefs.HEURISTICS_THROTTLE_TIMEOUT_PREF, throttleTimeout); + Preferences.set(prefs.HEURISTICS_THROTTLE_RATE_LIMIT_PREF, rateLimit); + + // Set up a passing environment and enable DoH. + let throttledPromise = TestUtils.topicObserved(throttleDoneTopic); + setPassingHeuristics(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + await ensureTRRMode(2); + info("waiting for throttle done"); + await throttledPromise; + await checkHeuristicsTelemetry("enable_doh", "startup"); + + // Change the environment to failing and simulate a network change. + throttledPromise = TestUtils.topicObserved(throttleDoneTopic); + simulateNetworkChange(); + info("waiting for throttle done"); + await throttledPromise; + await checkHeuristicsTelemetry("enable_doh", "netchange"); + + /* Simulate two consecutive network changes and check that we throttled the + * second heuristics run. */ + + // We wait for the throttle timer to fire twice - the first time, a fresh + // heuristics run will be performed since it was queued while throttling. + // This triggers another throttle timeout which is the second one we wait for. + throttledPromise = TestUtils.topicObserved(throttleDoneTopic).then(() => + TestUtils.topicObserved(throttleDoneTopic) + ); + simulateNetworkChange(); + simulateNetworkChange(); + info("waiting for throttle done"); + await throttledPromise; + await checkHeuristicsTelemetryMultiple(["netchange", "throttled"]); + + /* Simulate several consecutive network changes at a rate that exceeds the + * rate limit and check that we only record two heuristics runs in total - + * one for the initial netchange and one throttled run at the end. */ + + // We wait for the throttle timer to be extended twice. + let throttleExtendPromise = TestUtils.topicObserved(throttleExtendTopic); + let throttleExtendPromise2 = throttleExtendPromise.then(() => + TestUtils.topicObserved(throttleExtendTopic) + ); + + // Again, we wait for the timer to fire twice - once for the volatile period + // which results in a throttled heuristics run, and once after it with no run. + throttledPromise = throttleExtendPromise2 + .then(() => TestUtils.topicObserved(throttleDoneTopic)) + .then(() => TestUtils.topicObserved(throttleDoneTopic)); + + // Simulate three network changes: + // - The first one starts the throttle timer + // - The second one is within the limit of 1. + // - The third one exceeds the limit and extends the throttle period. + simulateNetworkChange(); + simulateNetworkChange(); + simulateNetworkChange(); + + // First throttle extension should happen now. + info("waiting for throttle extend"); + await throttleExtendPromise; + + // Two more network changes to once again extend the throttle period. + simulateNetworkChange(); + simulateNetworkChange(); + + // Now the second extension should be detected. + info("waiting for throttle done"); + await throttleExtendPromise2; + + // Finally, we wait for the throttle period to finish. + info("waiting for throttle done"); + await throttledPromise; + + await checkHeuristicsTelemetryMultiple(["netchange", "throttled"]); +}); diff --git a/browser/components/doh/test/browser/browser_trrSelect.js b/browser/components/doh/test/browser/browser_trrSelect.js new file mode 100644 index 0000000000..68861be8b8 --- /dev/null +++ b/browser/components/doh/test/browser/browser_trrSelect.js @@ -0,0 +1,147 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +async function waitForStartup() { + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); +} + +async function setPrefAndWaitForConfigFlush(pref, value) { + let configFlushed = DoHTestUtils.waitForConfigFlush(); + if (value) { + Preferences.set(pref, value); + } else { + Preferences.reset(pref); + } + await configFlushed; + await waitForStartup(); +} + +add_task(setup); + +add_task(async function testTRRSelect() { + // Clean start: doh-rollout.uri should be set after init. + setPassingHeuristics(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete." + ); + + // Wait for heuristics to complete. + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + // Reset and restart the controller for good measure. + Preferences.reset(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF); + Preferences.reset(prefs.TRR_SELECT_URI_PREF); + await restartDoHController(); + await waitForStartup(); + + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete." + ); + + // Disable committing. The committed URI should be reset to the + // default provider and the dry-run-result should persist. + prefPromise = TestUtils.waitForPrefChange( + prefs.TRR_SELECT_URI_PREF, + newVal => newVal == "https://example.com/1" + ); + await setPrefAndWaitForConfigFlush(prefs.TRR_SELECT_COMMIT_PREF, false); + await prefPromise; + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/1", + "Default TRR selected." + ); + try { + await BrowserTestUtils.waitForCondition(() => { + return !Preferences.isSet(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF); + }); + ok(false, "Dry run result was cleared, fail!"); + } catch (e) { + ok(true, "Dry run result was not cleared."); + } + is( + Preferences.get(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF), + "https://example.com/dns-query", + "dry-run result has the correct value." + ); + + // Reset again, dry-run-result should be recorded but not + // be committed. Committing is still disabled from above. + Preferences.reset(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF); + Preferences.reset(prefs.TRR_SELECT_URI_PREF); + await restartDoHController(); + await waitForStartup(); + + try { + await BrowserTestUtils.waitForCondition(() => { + return ( + Preferences.get(prefs.TRR_SELECT_URI_PREF) == + "https://example.com/dns-query" + ); + }); + ok(false, "Dry run result got committed, fail!"); + } catch (e) { + ok(true, "Dry run result did not get committed"); + } + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/1", + "Default TRR selected." + ); + is( + Preferences.get(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF), + "https://example.com/dns-query", + "TRR selection complete, dry-run result recorded." + ); + + // Reset doh-rollout.uri, and change the dry-run-result to another one on the + // default list. After init, the existing dry-run-result should be committed. + Preferences.reset(prefs.TRR_SELECT_URI_PREF); + Preferences.set( + prefs.TRR_SELECT_DRY_RUN_RESULT_PREF, + "https://example.com/2" + ); + prefPromise = TestUtils.waitForPrefChange( + prefs.TRR_SELECT_URI_PREF, + newVal => newVal == "https://example.com/2" + ); + await setPrefAndWaitForConfigFlush(prefs.TRR_SELECT_COMMIT_PREF, true); + await prefPromise; + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/2", + "TRR selection complete, existing dry-run-result committed." + ); + + // Reset doh-rollout.uri, and change the dry-run-result to another one NOT on + // default list. After init, a new TRR should be selected and committed. + prefPromise = TestUtils.waitForPrefChange( + prefs.TRR_SELECT_URI_PREF, + newVal => newVal == "https://example.com/dns-query" + ); + Preferences.reset(prefs.TRR_SELECT_URI_PREF); + Preferences.set( + prefs.TRR_SELECT_DRY_RUN_RESULT_PREF, + "https://example.com/4" + ); + await restartDoHController(); + await prefPromise; + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete, existing dry-run-result discarded and refreshed." + ); +}); diff --git a/browser/components/doh/test/browser/browser_trrSelection_disable.js b/browser/components/doh/test/browser/browser_trrSelection_disable.js new file mode 100644 index 0000000000..dc7bd68262 --- /dev/null +++ b/browser/components/doh/test/browser/browser_trrSelection_disable.js @@ -0,0 +1,74 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +add_task(async function testTrrSelectionDisable() { + // Turn off TRR Selection. + let configFlushed = DoHTestUtils.waitForConfigFlush(); + Preferences.set(prefs.TRR_SELECT_ENABLED_PREF, false); + await configFlushed; + + // Set up a passing environment and enable DoH. + setPassingHeuristics(); + let promise = waitForDoorhanger(); + Preferences.set(prefs.ENABLED_PREF, true); + await BrowserTestUtils.waitForCondition(() => { + return Preferences.get(prefs.BREADCRUMB_PREF); + }); + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + is( + Preferences.get(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF), + undefined, + "TRR selection dry run not performed." + ); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/1", + "doh-rollout.uri set to first provider in the list." + ); + ensureNoTRRSelectionTelemetry(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EXAMPLE_URL); + let panel = await promise; + + // Click the doorhanger's "accept" button. + let button = panel.querySelector(".popup-notification-primary-button"); + promise = BrowserTestUtils.waitForEvent(panel, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(button, {}); + await promise; + + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + await BrowserTestUtils.waitForCondition(() => { + return Preferences.get(prefs.DOORHANGER_USER_DECISION_PREF); + }); + is( + Preferences.get(prefs.DOORHANGER_USER_DECISION_PREF), + "UIOk", + "Doorhanger decision saved." + ); + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb not cleared."); + + BrowserTestUtils.removeTab(tab); + + // Restart the controller for good measure. + await restartDoHController(); + ensureNoTRRSelectionTelemetry(); + is( + Preferences.get(prefs.TRR_SELECT_DRY_RUN_RESULT_PREF), + undefined, + "TRR selection dry run not performed." + ); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/1", + "doh-rollout.uri set to first provider in the list." + ); + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); +}); diff --git a/browser/components/doh/test/browser/browser_userInterference.js b/browser/components/doh/test/browser/browser_userInterference.js new file mode 100644 index 0000000000..96d1e3c94c --- /dev/null +++ b/browser/components/doh/test/browser/browser_userInterference.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +add_task(setup); + +add_task(async function testUserInterference() { + // Set up a passing environment and enable DoH. + setPassingHeuristics(); + let promise = waitForDoorhanger(); + let prefPromise = TestUtils.waitForPrefChange(prefs.BREADCRUMB_PREF); + Preferences.set(prefs.ENABLED_PREF, true); + + await prefPromise; + is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved."); + is( + Preferences.get(prefs.TRR_SELECT_URI_PREF), + "https://example.com/dns-query", + "TRR selection complete." + ); + await checkTRRSelectionTelemetry(); + + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, EXAMPLE_URL); + let panel = await promise; + + prefPromise = TestUtils.waitForPrefChange( + prefs.DOORHANGER_USER_DECISION_PREF + ); + + // Click the doorhanger's "accept" button. + let button = panel.querySelector(".popup-notification-primary-button"); + promise = BrowserTestUtils.waitForEvent(panel, "popuphidden"); + EventUtils.synthesizeMouseAtCenter(button, {}); + await promise; + await prefPromise; + + is( + Preferences.get(prefs.DOORHANGER_USER_DECISION_PREF), + "UIOk", + "Doorhanger decision saved." + ); + + BrowserTestUtils.removeTab(tab); + + await ensureTRRMode(2); + await checkHeuristicsTelemetry("enable_doh", "startup"); + + // Set the TRR mode pref manually and ensure we respect this. + Preferences.set(prefs.NETWORK_TRR_MODE_PREF, 3); + await ensureTRRMode(undefined); + + // Simulate a network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + is( + Preferences.get(prefs.DISABLED_PREF, false), + true, + "Manual disable recorded." + ); + is(Preferences.get(prefs.BREADCRUMB_PREF), undefined, "Breadcrumb cleared."); + + // Simulate another network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); + + // Restart the controller for good measure. + await restartDoHController(); + await ensureNoTRRModeChange(undefined); + ensureNoTRRSelectionTelemetry(); + ensureNoHeuristicsTelemetry(); + + // Simulate another network change. + simulateNetworkChange(); + await ensureNoTRRModeChange(undefined); + ensureNoHeuristicsTelemetry(); +}); diff --git a/browser/components/doh/test/browser/head.js b/browser/components/doh/test/browser/head.js new file mode 100644 index 0000000000..20d8156cc0 --- /dev/null +++ b/browser/components/doh/test/browser/head.js @@ -0,0 +1,365 @@ +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + DoHConfigController: "resource:///modules/DoHConfig.sys.mjs", + DoHController: "resource:///modules/DoHController.sys.mjs", + DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs", + Preferences: "resource://gre/modules/Preferences.sys.mjs", + Region: "resource://gre/modules/Region.sys.mjs", + RegionTestUtils: "resource://testing-common/RegionTestUtils.sys.mjs", + RemoteSettings: "resource://services-settings/remote-settings.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(this, { + ASRouter: "resource://activity-stream/lib/ASRouter.jsm", +}); + +XPCOMUtils.defineLazyServiceGetter( + this, + "gDNSOverride", + "@mozilla.org/network/native-dns-override;1", + "nsINativeDNSResolverOverride" +); + +const { CommonUtils } = ChromeUtils.importESModule( + "resource://services-common/utils.sys.mjs" +); + +const EXAMPLE_URL = "https://example.com/"; + +const prefs = { + TESTING_PREF: "doh-rollout._testing", + ENABLED_PREF: "doh-rollout.enabled", + ROLLOUT_TRR_MODE_PREF: "doh-rollout.mode", + NETWORK_TRR_MODE_PREF: "network.trr.mode", + CONFIRMATION_NS_PREF: "network.trr.confirmationNS", + BREADCRUMB_PREF: "doh-rollout.self-enabled", + DOORHANGER_USER_DECISION_PREF: "doh-rollout.doorhanger-decision", + DISABLED_PREF: "doh-rollout.disable-heuristics", + SKIP_HEURISTICS_PREF: "doh-rollout.skipHeuristicsCheck", + CLEAR_ON_SHUTDOWN_PREF: "doh-rollout.clearModeOnShutdown", + FIRST_RUN_PREF: "doh-rollout.doneFirstRun", + PROVIDER_LIST_PREF: "doh-rollout.provider-list", + TRR_SELECT_ENABLED_PREF: "doh-rollout.trr-selection.enabled", + TRR_SELECT_URI_PREF: "doh-rollout.uri", + TRR_SELECT_COMMIT_PREF: "doh-rollout.trr-selection.commit-result", + TRR_SELECT_DRY_RUN_RESULT_PREF: "doh-rollout.trr-selection.dry-run-result", + PROVIDER_STEERING_PREF: "doh-rollout.provider-steering.enabled", + PROVIDER_STEERING_LIST_PREF: "doh-rollout.provider-steering.provider-list", + NETWORK_DEBOUNCE_TIMEOUT_PREF: "doh-rollout.network-debounce-timeout", + HEURISTICS_THROTTLE_TIMEOUT_PREF: "doh-rollout.heuristics-throttle-timeout", + HEURISTICS_THROTTLE_RATE_LIMIT_PREF: + "doh-rollout.heuristics-throttle-rate-limit", +}; + +const CFR_PREF = "browser.newtabpage.activity-stream.asrouter.providers.cfr"; +const CFR_JSON = { + id: "cfr", + enabled: true, + type: "local", + localProvider: "CFRMessageProvider", + categories: ["cfrAddons", "cfrFeatures"], +}; + +async function setup() { + await DoHController._uninit(); + await DoHConfigController._uninit(); + SpecialPowers.pushPrefEnv({ + set: [["security.notification_enable_delay", 0]], + }); + let oldCanRecord = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + Services.telemetry.clearEvents(); + + // Enable the CFR. + Preferences.set(CFR_PREF, JSON.stringify(CFR_JSON)); + + // Tell DoHController that this isn't real life. + Preferences.set(prefs.TESTING_PREF, true); + + // Avoid non-local connections to the TRR endpoint. + Preferences.set(prefs.CONFIRMATION_NS_PREF, "skip"); + + // Enable trr selection and provider steeringfor tests. This is off + // by default so it can be controlled via Normandy. + Preferences.set(prefs.TRR_SELECT_ENABLED_PREF, true); + Preferences.set(prefs.PROVIDER_STEERING_PREF, true); + + // Enable committing the TRR selection. This pref ships false by default so + // it can be controlled e.g. via Normandy, but for testing let's set enable. + Preferences.set(prefs.TRR_SELECT_COMMIT_PREF, true); + + // Clear mode on shutdown by default. + Preferences.set(prefs.CLEAR_ON_SHUTDOWN_PREF, true); + + // Generally don't bother with debouncing or throttling. + // The throttling test will set this explicitly. + Preferences.set(prefs.NETWORK_DEBOUNCE_TIMEOUT_PREF, -1); + Preferences.set(prefs.HEURISTICS_THROTTLE_TIMEOUT_PREF, -1); + + // Set up heuristics, all passing by default. + + // Google safesearch overrides + gDNSOverride.addIPOverride("www.google.com.", "1.1.1.1"); + gDNSOverride.addIPOverride("google.com.", "1.1.1.1"); + gDNSOverride.addIPOverride("forcesafesearch.google.com.", "1.1.1.2"); + + // YouTube safesearch overrides + gDNSOverride.addIPOverride("www.youtube.com.", "2.1.1.1"); + gDNSOverride.addIPOverride("m.youtube.com.", "2.1.1.1"); + gDNSOverride.addIPOverride("youtubei.googleapis.com.", "2.1.1.1"); + gDNSOverride.addIPOverride("youtube.googleapis.com.", "2.1.1.1"); + gDNSOverride.addIPOverride("www.youtube-nocookie.com.", "2.1.1.1"); + gDNSOverride.addIPOverride("restrict.youtube.com.", "2.1.1.2"); + gDNSOverride.addIPOverride("restrictmoderate.youtube.com.", "2.1.1.2"); + + // Zscaler override + gDNSOverride.addIPOverride("sitereview.zscaler.com.", "3.1.1.1"); + + // Global canary + gDNSOverride.addIPOverride("use-application-dns.net.", "4.1.1.1"); + + await DoHTestUtils.resetRemoteSettingsConfig(false); + + await DoHConfigController.init(); + await DoHController.init(); + + await waitForStateTelemetry(["rollback"]); + + registerCleanupFunction(async () => { + Services.telemetry.canRecordExtended = oldCanRecord; + Services.telemetry.clearEvents(); + gDNSOverride.clearOverrides(); + if (ASRouter.state.messageBlockList.includes("DOH_ROLLOUT_CONFIRMATION")) { + await ASRouter.unblockMessageById("DOH_ROLLOUT_CONFIRMATION"); + } + // The CFR pref is set to an empty array in user.js for testing profiles, + // so "reset" it back to that value. + Preferences.set(CFR_PREF, "[]"); + await DoHController._uninit(); + Services.telemetry.clearEvents(); + Preferences.reset(Object.values(prefs)); + await DoHTestUtils.resetRemoteSettingsConfig(false); + await DoHController.init(); + }); +} + +const kTestRegion = "DE"; +const kRegionalPrefNamespace = `doh-rollout.${kTestRegion.toLowerCase()}`; + +async function setupRegion() { + Region._home = null; + RegionTestUtils.setNetworkRegion(kTestRegion); + await Region._fetchRegion(); + is(Region.home, kTestRegion, "Should have correct region"); + Preferences.reset("doh-rollout.home-region"); + await DoHConfigController.loadRegion(); +} + +async function checkTRRSelectionTelemetry() { + let events; + await TestUtils.waitForCondition(() => { + events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + return events && events.length; + }); + events = events.filter( + e => + e[1] == "security.doh.trrPerformance" && + e[2] == "trrselect" && + e[3] == "dryrunresult" + ); + is(events.length, 1, "Found the expected trrselect event."); + is( + events[0][4], + "https://example.com/dns-query", + "The event records the expected decision" + ); +} + +function ensureNoTRRSelectionTelemetry() { + let events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + if (!events) { + ok(true, "Found no trrselect events."); + return; + } + events = events.filter( + e => + e[1] == "security.doh.trrPerformance" && + e[2] == "trrselect" && + e[3] == "dryrunresult" + ); + is(events.length, 0, "Found no trrselect events."); +} + +async function checkHeuristicsTelemetry( + decision, + evaluateReason, + steeredProvider = "" +) { + let events; + await TestUtils.waitForCondition(() => { + events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + events = events?.filter( + e => e[1] == "doh" && e[2] == "evaluate_v2" && e[3] == "heuristics" + ); + return events?.length; + }); + is(events.length, 1, "Found the expected heuristics event."); + is(events[0][4], decision, "The event records the expected decision"); + if (evaluateReason) { + is(events[0][5].evaluateReason, evaluateReason, "Got the expected reason."); + } + is(events[0][5].steeredProvider, steeredProvider, "Got expected provider."); + + // After checking the event, clear all telemetry. Since we check for a single + // event above, this ensures all heuristics events are intentional and tested. + // TODO: Test events other than heuristics. Those tests would also work the + // same way, so as to test one event at a time, and this clearEvents() call + // will continue to exist as-is. + Services.telemetry.clearEvents(); +} + +async function checkHeuristicsTelemetryMultiple(expectedEvaluateReasons) { + let events; + await TestUtils.waitForCondition(() => { + events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + if (events && events.length) { + events = events.filter( + e => e[1] == "doh" && e[2] == "evaluate_v2" && e[3] == "heuristics" + ); + if (events.length == expectedEvaluateReasons.length) { + return true; + } + } + return false; + }); + is( + events.length, + expectedEvaluateReasons.length, + "Found the expected heuristics events." + ); + for (let reason of expectedEvaluateReasons) { + let event = events.find(e => e[5].evaluateReason == reason); + is(event[5].evaluateReason, reason, `${reason} event found`); + } + Services.telemetry.clearEvents(); +} + +function ensureNoHeuristicsTelemetry() { + let events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + if (!events) { + ok(true, "Found no heuristics events."); + return; + } + events = events.filter( + e => e[1] == "doh" && e[2] == "evaluate_v2" && e[3] == "heuristics" + ); + is(events.length, 0, "Found no heuristics events."); +} + +async function waitForStateTelemetry(expectedStates) { + let events; + await TestUtils.waitForCondition(() => { + events = Services.telemetry.snapshotEvents( + Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS + ).parent; + return events; + }); + events = events.filter(e => e[1] == "doh" && e[2] == "state"); + info(events); + is(events.length, expectedStates.length, "Found the expected state events."); + for (let state of expectedStates) { + let event = events.find(e => e[3] == state); + is(event[3], state, `${state} state found`); + } + Services.telemetry.clearEvents(); +} + +async function restartDoHController() { + let oldMode = Preferences.get(prefs.ROLLOUT_TRR_MODE_PREF); + await DoHController._uninit(); + let newMode = Preferences.get(prefs.ROLLOUT_TRR_MODE_PREF); + let expectClear = Preferences.get(prefs.CLEAR_ON_SHUTDOWN_PREF); + is( + newMode, + expectClear ? undefined : oldMode, + `Mode was ${expectClear ? "cleared" : "persisted"} on shutdown.` + ); + await DoHController.init(); +} + +// setPassing/FailingHeuristics are used generically to test that DoH is enabled +// or disabled correctly. We use the zscaler canary arbitrarily here, individual +// heuristics are tested separately. +function setPassingHeuristics() { + gDNSOverride.clearHostOverride("sitereview.zscaler.com."); + gDNSOverride.addIPOverride("sitereview.zscaler.com.", "3.1.1.1"); +} + +function setFailingHeuristics() { + gDNSOverride.clearHostOverride("sitereview.zscaler.com."); + gDNSOverride.addIPOverride("sitereview.zscaler.com.", "213.152.228.242"); +} + +async function waitForDoorhanger() { + const popupID = "contextual-feature-recommendation"; + const bucketID = "DOH_ROLLOUT_CONFIRMATION"; + let panel; + await BrowserTestUtils.waitForEvent(document, "popupshown", true, event => { + panel = event.originalTarget; + let popupNotification = event.originalTarget.firstChild; + return ( + popupNotification && + popupNotification.notification && + popupNotification.notification.id == popupID && + popupNotification.getAttribute("data-notification-bucket") == bucketID + ); + }); + return panel; +} + +function simulateNetworkChange() { + // The networkStatus API does not actually propagate the link status we supply + // here, but rather sends the link status from the NetworkLinkService. + // This means there's no point sending a down and then an up - the extension + // will just receive "up" twice. + // TODO: Implement a mock NetworkLinkService and use it to also simulate + // network down events. + Services.obs.notifyObservers(null, "network:link-status-changed", "up"); +} + +async function ensureTRRMode(mode) { + await TestUtils.waitForCondition(() => { + return Preferences.get(prefs.ROLLOUT_TRR_MODE_PREF) === mode; + }); + is(Preferences.get(prefs.ROLLOUT_TRR_MODE_PREF), mode, `TRR mode is ${mode}`); +} + +async function ensureNoTRRModeChange(mode) { + try { + // Try and wait for the TRR pref to change... waitForCondition should throw + // after trying for a while. + await TestUtils.waitForCondition(() => { + return Preferences.get(prefs.ROLLOUT_TRR_MODE_PREF) !== mode; + }); + // If we reach this, the waitForCondition didn't throw. Fail! + ok(false, "TRR mode changed when it shouldn't have!"); + } catch (e) { + // Assert for clarity. + is( + Preferences.get(prefs.ROLLOUT_TRR_MODE_PREF), + mode, + "No change in TRR mode" + ); + } +} |