diff options
Diffstat (limited to 'browser/components/preferences/tests/browser_connection_dnsoverhttps.js')
-rw-r--r-- | browser/components/preferences/tests/browser_connection_dnsoverhttps.js | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/browser/components/preferences/tests/browser_connection_dnsoverhttps.js b/browser/components/preferences/tests/browser_connection_dnsoverhttps.js new file mode 100644 index 0000000000..e7adabe6c0 --- /dev/null +++ b/browser/components/preferences/tests/browser_connection_dnsoverhttps.js @@ -0,0 +1,496 @@ +var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + +ChromeUtils.defineModuleGetter( + this, + "DoHController", + "resource:///modules/DoHController.jsm" +); + +const SUBDIALOG_URL = + "chrome://browser/content/preferences/dialogs/connection.xhtml"; +const TRR_MODE_PREF = "network.trr.mode"; +const TRR_URI_PREF = "network.trr.uri"; +const TRR_RESOLVERS_PREF = "network.trr.resolvers"; +const TRR_CUSTOM_URI_PREF = "network.trr.custom_uri"; +const ROLLOUT_ENABLED_PREF = "doh-rollout.enabled"; +const ROLLOUT_SELF_ENABLED_PREF = "doh-rollout.self-enabled"; +const HEURISTICS_DISABLED_PREF = "doh-rollout.disable-heuristics"; +const DEFAULT_RESOLVER_VALUE = "https://mozilla.cloudflare-dns.com/dns-query"; +const NEXTDNS_RESOLVER_VALUE = "https://firefox.dns.nextdns.io/"; + +const modeCheckboxSelector = "#networkDnsOverHttps"; +const uriTextboxSelector = "#networkCustomDnsOverHttpsInput"; +const resolverMenulistSelector = "#networkDnsOverHttpsResolverChoices"; +const defaultPrefValues = Object.freeze({ + [TRR_MODE_PREF]: 0, + [TRR_URI_PREF]: "https://mozilla.cloudflare-dns.com/dns-query", + [TRR_RESOLVERS_PREF]: JSON.stringify([ + { name: "Cloudflare", url: DEFAULT_RESOLVER_VALUE }, + { name: "example.org", url: "https://example.org/dns-query" }, + ]), + [TRR_CUSTOM_URI_PREF]: "", +}); + +async function resetPrefs() { + await DoHController._uninit(); + Services.prefs.clearUserPref(TRR_MODE_PREF); + Services.prefs.clearUserPref(TRR_URI_PREF); + Services.prefs.clearUserPref(TRR_RESOLVERS_PREF); + Services.prefs.clearUserPref(TRR_CUSTOM_URI_PREF); + Services.prefs.getChildList("doh-rollout.").forEach(pref => { + Services.prefs.clearUserPref(pref); + }); + // Clear out any telemetry events generated by DoHController so that we don't + // confuse tests running after this one that are looking at those. + Services.telemetry.clearEvents(); + await DoHController.init(); +} + +let preferencesOpen = new Promise(res => open_preferences(res)); + +registerCleanupFunction(() => { + resetPrefs(); + gBrowser.removeCurrentTab(); +}); + +async function openConnectionsSubDialog() { + /* + The connection dialog has type="child", So it has to be opened as a sub dialog + of the main pref tab. Prefs only get updated after the subdialog is confirmed & closed + */ + let dialog = await openAndLoadSubDialog(SUBDIALOG_URL); + ok(dialog, "connection window opened"); + return dialog; +} + +function waitForPrefObserver(name) { + return new Promise(resolve => { + const observer = { + observe(aSubject, aTopic, aData) { + if (aData == name) { + Services.prefs.removeObserver(name, observer); + resolve(); + } + }, + }; + Services.prefs.addObserver(name, observer); + }); +} + +async function testWithProperties(props, startTime) { + info( + Date.now() - + startTime + + ": testWithProperties: testing with " + + JSON.stringify(props) + ); + + // There are two different signals that the DoHController is ready, depending + // on the config being tested. If we're setting the TRR mode pref, we should + // expect the disable-heuristics pref to be set as the signal. Else, we can + // expect the self-enabled pref as the signal. + let rolloutReadyPromise; + if (props.hasOwnProperty(TRR_MODE_PREF)) { + if ( + [2, 3, 5].includes(props[TRR_MODE_PREF]) && + props.hasOwnProperty(ROLLOUT_ENABLED_PREF) + ) { + // Only initialize the promise if we're going to enable the rollout - + // otherwise we will never await it, which could cause a leak if it doesn't + // end up resolving. + rolloutReadyPromise = waitForPrefObserver(HEURISTICS_DISABLED_PREF); + } + Services.prefs.setIntPref(TRR_MODE_PREF, props[TRR_MODE_PREF]); + } + if (props.hasOwnProperty(ROLLOUT_ENABLED_PREF)) { + if (!rolloutReadyPromise) { + rolloutReadyPromise = waitForPrefObserver(ROLLOUT_SELF_ENABLED_PREF); + } + Services.prefs.setBoolPref( + ROLLOUT_ENABLED_PREF, + props[ROLLOUT_ENABLED_PREF] + ); + await rolloutReadyPromise; + } + if (props.hasOwnProperty(TRR_CUSTOM_URI_PREF)) { + Services.prefs.setStringPref( + TRR_CUSTOM_URI_PREF, + props[TRR_CUSTOM_URI_PREF] + ); + } + if (props.hasOwnProperty(TRR_URI_PREF)) { + Services.prefs.setStringPref(TRR_URI_PREF, props[TRR_URI_PREF]); + } + if (props.hasOwnProperty(TRR_RESOLVERS_PREF)) { + info(`Setting ${TRR_RESOLVERS_PREF} to ${props[TRR_RESOLVERS_PREF]}`); + Services.prefs.setStringPref(TRR_RESOLVERS_PREF, props[TRR_RESOLVERS_PREF]); + } + + let dialog = await openConnectionsSubDialog(); + await dialog.uiReady; + info( + Date.now() - startTime + ": testWithProperties: connections dialog now open" + ); + let doc = dialog.document; + let win = doc.ownerGlobal; + let dialogElement = doc.getElementById("ConnectionsDialog"); + let dialogClosingPromise = BrowserTestUtils.waitForEvent( + dialogElement, + "dialogclosing" + ); + let modeCheckbox = doc.querySelector(modeCheckboxSelector); + let uriTextbox = doc.querySelector(uriTextboxSelector); + let resolverMenulist = doc.querySelector(resolverMenulistSelector); + let uriPrefChangedPromise; + let modePrefChangedPromise; + let disableHeuristicsPrefChangedPromise; + + if (props.hasOwnProperty("expectedModeChecked")) { + await TestUtils.waitForCondition( + () => modeCheckbox.checked === props.expectedModeChecked + ); + is( + modeCheckbox.checked, + props.expectedModeChecked, + "mode checkbox has expected checked state" + ); + } + if (props.hasOwnProperty("expectedUriValue")) { + await TestUtils.waitForCondition( + () => uriTextbox.value === props.expectedUriValue + ); + is( + uriTextbox.value, + props.expectedUriValue, + "URI textbox has expected value" + ); + } + if (props.hasOwnProperty("expectedResolverListValue")) { + await TestUtils.waitForCondition( + () => resolverMenulist.value === props.expectedResolverListValue + ); + is( + resolverMenulist.value, + props.expectedResolverListValue, + "resolver menulist has expected value" + ); + } + if (props.clickMode) { + info( + Date.now() - + startTime + + ": testWithProperties: clickMode, waiting for the pref observer" + ); + modePrefChangedPromise = waitForPrefObserver(TRR_MODE_PREF); + if (props.hasOwnProperty("expectedDisabledHeuristics")) { + disableHeuristicsPrefChangedPromise = waitForPrefObserver( + HEURISTICS_DISABLED_PREF + ); + } + info( + Date.now() - startTime + ": testWithProperties: clickMode, pref changed" + ); + modeCheckbox.scrollIntoView(); + EventUtils.synthesizeMouseAtCenter(modeCheckbox, {}, win); + info( + Date.now() - + startTime + + ": testWithProperties: clickMode, mouse click synthesized" + ); + } + if (props.hasOwnProperty("selectResolver")) { + info( + Date.now() - + startTime + + ": testWithProperties: selectResolver, creating change event" + ); + resolverMenulist.focus(); + resolverMenulist.value = props.selectResolver; + resolverMenulist.dispatchEvent(new Event("input", { bubbles: true })); + resolverMenulist.dispatchEvent(new Event("change", { bubbles: true })); + info( + Date.now() - + startTime + + ": testWithProperties: selectResolver, item value set and events dispatched" + ); + } + + if (props.hasOwnProperty("inputUriKeys")) { + info( + Date.now() - + startTime + + ": testWithProperties: inputUriKeys, waiting for the pref observer" + ); + uriPrefChangedPromise = waitForPrefObserver(TRR_CUSTOM_URI_PREF); + info( + Date.now() - + startTime + + ": testWithProperties: inputUriKeys, pref changed, now enter the new value" + ); + uriTextbox.focus(); + uriTextbox.value = props.inputUriKeys; + uriTextbox.dispatchEvent(new win.Event("input", { bubbles: true })); + uriTextbox.dispatchEvent(new win.Event("change", { bubbles: true })); + info( + Date.now() - + startTime + + ": testWithProperties: inputUriKeys, input and change events dispatched" + ); + } + + info(Date.now() - startTime + ": testWithProperties: calling acceptDialog"); + dialogElement.acceptDialog(); + + info( + Date.now() - + startTime + + ": testWithProperties: waiting for the dialogClosingPromise" + ); + let dialogClosingEvent = await dialogClosingPromise; + ok(dialogClosingEvent, "connection window closed"); + + info( + Date.now() - + startTime + + ": testWithProperties: waiting for any of uri and mode prefs to change" + ); + await Promise.all([ + uriPrefChangedPromise, + modePrefChangedPromise, + disableHeuristicsPrefChangedPromise, + ]); + info(Date.now() - startTime + ": testWithProperties: prefs changed"); + + if (props.hasOwnProperty("expectedFinalUriPref")) { + let uriPref = Services.prefs.getStringPref(TRR_URI_PREF); + is( + uriPref, + props.expectedFinalUriPref, + "uri pref ended up with the expected value" + ); + } + + if (props.hasOwnProperty("expectedModePref")) { + let modePref = Services.prefs.getIntPref(TRR_MODE_PREF); + is( + modePref, + props.expectedModePref, + "mode pref ended up with the expected value" + ); + } + + if (props.hasOwnProperty("expectedDisabledHeuristics")) { + let disabledHeuristicsPref = Services.prefs.getBoolPref( + HEURISTICS_DISABLED_PREF + ); + is( + disabledHeuristicsPref, + props.expectedDisabledHeuristics, + "disable-heuristics pref ended up with the expected value" + ); + } + + if (props.hasOwnProperty("expectedFinalCusomUriPref")) { + let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF); + is( + customUriPref, + props.expectedFinalCustomUriPref, + "custom_uri pref ended up with the expected value" + ); + } + + info(Date.now() - startTime + ": testWithProperties: fin"); +} + +add_task(async function default_values() { + let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF); + let uriPref = Services.prefs.getStringPref(TRR_URI_PREF); + let modePref = Services.prefs.getIntPref(TRR_MODE_PREF); + is( + modePref, + defaultPrefValues[TRR_MODE_PREF], + `Actual value of ${TRR_MODE_PREF} matches expected default value` + ); + is( + uriPref, + defaultPrefValues[TRR_URI_PREF], + `Actual value of ${TRR_URI_PREF} matches expected default value` + ); + is( + customUriPref, + defaultPrefValues[TRR_CUSTOM_URI_PREF], + `Actual value of ${TRR_CUSTOM_URI_PREF} matches expected default value` + ); +}); + +let testVariations = [ + // verify state with defaults + { name: "default", expectedModePref: 0, expectedUriValue: "" }, + + // verify each of the modes maps to the correct checked state + { name: "mode 0", [TRR_MODE_PREF]: 0, expectedModeChecked: false }, + { + name: "mode 1", + [TRR_MODE_PREF]: 1, + expectedModeChecked: false, + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + }, + { + name: "mode 2", + [TRR_MODE_PREF]: 2, + expectedModeChecked: true, + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + }, + { + name: "mode 3", + [TRR_MODE_PREF]: 3, + expectedModeChecked: true, + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + }, + { + name: "mode 4", + [TRR_MODE_PREF]: 4, + expectedModeChecked: false, + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + }, + { name: "mode 5", [TRR_MODE_PREF]: 5, expectedModeChecked: false }, + // verify an out of bounds mode value maps to the correct checked state + { + name: "mode out-of-bounds", + [TRR_MODE_PREF]: 77, + expectedModeChecked: false, + }, + + // verify automatic heuristics states + { + name: "heuristics on and mode unset", + [TRR_MODE_PREF]: 0, + [ROLLOUT_ENABLED_PREF]: true, + expectedModeChecked: true, + }, + { + name: "heuristics on and mode set to 2", + [TRR_MODE_PREF]: 2, + [ROLLOUT_ENABLED_PREF]: true, + expectedModeChecked: true, + }, + { + name: "heuristics on but disabled, mode unset", + [TRR_MODE_PREF]: 5, + [ROLLOUT_ENABLED_PREF]: true, + expectedModeChecked: false, + }, + { + name: "heuristics on but disabled, mode set to 2", + [TRR_MODE_PREF]: 2, + [ROLLOUT_ENABLED_PREF]: true, + expectedModeChecked: true, + }, + + // verify toggling the checkbox gives the right outcomes + { + name: "toggle mode on", + clickMode: true, + expectedModeValue: 2, + expectedUriValue: "", + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + }, + { + name: "toggle mode off", + [TRR_MODE_PREF]: 2, + expectedModeChecked: true, + clickMode: true, + expectedModePref: 5, + }, + { + name: "toggle mode off when on due to heuristics", + [TRR_MODE_PREF]: 0, + [ROLLOUT_ENABLED_PREF]: true, + expectedModeChecked: true, + clickMode: true, + expectedModePref: 5, + expectedDisabledHeuristics: true, + }, + // Test selecting non-default, non-custom TRR provider, NextDNS. + { + name: "Select NextDNS as TRR provider", + [TRR_MODE_PREF]: 2, + selectResolver: NEXTDNS_RESOLVER_VALUE, + expectedFinalUriPref: NEXTDNS_RESOLVER_VALUE, + }, + { + name: "return to default from NextDNS", + [TRR_MODE_PREF]: 2, + [TRR_URI_PREF]: NEXTDNS_RESOLVER_VALUE, + expectedResolverListValue: NEXTDNS_RESOLVER_VALUE, + selectResolver: DEFAULT_RESOLVER_VALUE, + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + }, + // test that selecting Custom, when we have a TRR_CUSTOM_URI_PREF subsequently changes TRR_URI_PREF + { + name: "select custom with existing custom_uri pref value", + [TRR_MODE_PREF]: 2, + [TRR_CUSTOM_URI_PREF]: "https://example.com", + expectedModeValue: true, + selectResolver: "custom", + expectedUriValue: "https://example.com", + expectedFinalUriPref: "https://example.com", + expectedFinalCustomUriPref: "https://example.com", + }, + { + name: "select custom and enter new custom_uri pref value", + [TRR_URI_PREF]: "", + [TRR_CUSTOM_URI_PREF]: "", + clickMode: true, + selectResolver: "custom", + inputUriKeys: "https://example.com", + expectedModePref: 2, + expectedFinalUriPref: "https://example.com", + expectedFinalCustomUriPref: "https://example.com", + }, + + { + name: "return to default from custom", + [TRR_MODE_PREF]: 2, + [TRR_URI_PREF]: "https://example.com", + [TRR_CUSTOM_URI_PREF]: "https://example.com", + expectedUriValue: "https://example.com", + expectedResolverListValue: "custom", + selectResolver: DEFAULT_RESOLVER_VALUE, + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + expectedFinalCustomUriPref: "https://example.com", + }, + { + name: "clear the custom uri", + [TRR_MODE_PREF]: 2, + [TRR_URI_PREF]: "https://example.com", + [TRR_CUSTOM_URI_PREF]: "https://example.com", + expectedUriValue: "https://example.com", + expectedResolverListValue: "custom", + inputUriKeys: "", + expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, + expectedFinalCustomUriPref: "", + }, + { + name: "empty default resolver list", + [TRR_RESOLVERS_PREF]: "", + [TRR_MODE_PREF]: 2, + [TRR_URI_PREF]: "https://example.com", + [TRR_CUSTOM_URI_PREF]: "", + [TRR_RESOLVERS_PREF]: "", + expectedUriValue: "https://example.com", + expectedResolverListValue: "custom", + expectedFinalUriPref: "https://example.com", + expectedFinalCustomUriPref: "https://example.com", + }, +]; + +for (let props of testVariations) { + add_task(async function testVariation() { + await preferencesOpen; + let startTime = Date.now(); + await resetPrefs(); + info("starting test: " + props.name); + await testWithProperties(props, startTime); + }); +} |