summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/tests/browser_connection_dnsoverhttps.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/preferences/tests/browser_connection_dnsoverhttps.js')
-rw-r--r--browser/components/preferences/tests/browser_connection_dnsoverhttps.js496
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);
+ });
+}