Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"use strict";
requestLongerTimeout(4);
const { EnterprisePolicyTesting, PoliciesPrefTracker } =
ChromeUtils.importESModule(
);
ChromeUtils.defineESModuleGetters(this, {
DoHConfigController: "resource:///modules/DoHConfig.sys.mjs",
DoHController: "resource:///modules/DoHController.sys.mjs",
});
const { MockRegistrar } = ChromeUtils.importESModule(
);
const TRR_MODE_PREF = "network.trr.mode";
const TRR_URI_PREF = "network.trr.uri";
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 FIRST_RESOLVER_VALUE = DoHTestUtils.providers[0].uri;
const SECOND_RESOLVER_VALUE = DoHTestUtils.providers[1].uri;
const DEFAULT_RESOLVER_VALUE = FIRST_RESOLVER_VALUE;
const defaultPrefValues = Object.freeze({
[TRR_MODE_PREF]: 0,
[TRR_CUSTOM_URI_PREF]: "",
});
// See bug 1741554. This test should not actually try to create a connection to
// the real DoH endpoint. But a background request could do that while the test
// is in progress, before we've actually disabled TRR, and would cause a crash
// due to connecting to a non-local IP.
// To prevent that we override the IP to a local address.
Cc["@mozilla.org/network/native-dns-override;1"]
.getService(Ci.nsINativeDNSResolverOverride)
.addIPOverride("mozilla.cloudflare-dns.com", "127.0.0.1");
async function clearEvents() {
Services.telemetry.clearEvents();
await TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_ALL_CHANNELS,
true
).parent;
return !events || !events.length;
});
}
async function getEvent(filter1, filter2) {
let event = await TestUtils.waitForCondition(() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_ALL_CHANNELS,
true
).parent;
return events?.find(e => e[1] == filter1 && e[2] == filter2);
}, "recorded telemetry for the load");
event.shift();
return event;
}
async function resetPrefs() {
await DoHTestUtils.resetRemoteSettingsConfig();
await DoHController._uninit();
Services.prefs.clearUserPref(TRR_MODE_PREF);
Services.prefs.clearUserPref(TRR_URI_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();
}
Services.prefs.setStringPref("network.trr.confirmationNS", "skip");
registerCleanupFunction(async () => {
await resetPrefs();
Services.prefs.clearUserPref("network.trr.confirmationNS");
});
add_setup(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["toolkit.telemetry.testing.overrideProductsCheck", true]],
});
await DoHTestUtils.resetRemoteSettingsConfig();
});
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);
});
}
// Mock parental controls service in order to enable it
let parentalControlsService = {
parentalControlsEnabled: true,
QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
};
let mockParentalControlsServiceCid = undefined;
async function setMockParentalControlEnabled(aEnabled) {
if (mockParentalControlsServiceCid != undefined) {
MockRegistrar.unregister(mockParentalControlsServiceCid);
mockParentalControlsServiceCid = undefined;
}
if (aEnabled) {
mockParentalControlsServiceCid = MockRegistrar.register(
"@mozilla.org/parental-controls-service;1",
parentalControlsService
);
}
Services.dns.reloadParentalControlEnabled();
}
add_task(async function testParentalControls() {
async function withConfiguration(configuration, fn) {
info("testParentalControls");
await resetPrefs();
Services.prefs.setIntPref(TRR_MODE_PREF, configuration.trr_mode);
await setMockParentalControlEnabled(configuration.parentalControlsState);
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
let statusElement = doc.getElementById("dohStatus");
await TestUtils.waitForCondition(() => {
return (
document.l10n.getAttributes(statusElement).args.status ==
configuration.wait_for_doh_status
);
});
await fn({
statusElement,
});
gBrowser.removeCurrentTab();
await setMockParentalControlEnabled(false);
}
info("Check parental controls disabled, TRR off");
await withConfiguration(
{
parentalControlsState: false,
trr_mode: 0,
wait_for_doh_status: "Off",
},
async res => {
is(
document.l10n.getAttributes(res.statusElement).args.status,
"Off",
"expecting status off"
);
}
);
info("Check parental controls enabled, TRR off");
await withConfiguration(
{
parentalControlsState: true,
trr_mode: 0,
wait_for_doh_status: "Off",
},
async res => {
is(
document.l10n.getAttributes(res.statusElement).args.status,
"Off",
"expecting status off"
);
}
);
// Enable the rollout.
await DoHTestUtils.loadRemoteSettingsConfig({
providers: "example",
rolloutEnabled: true,
steeringEnabled: false,
steeringProviders: "",
autoDefaultEnabled: false,
autoDefaultProviders: "",
id: "global",
});
info("Check parental controls disabled, TRR first");
await withConfiguration(
{
parentalControlsState: false,
trr_mode: 2,
wait_for_doh_status: "Active",
},
async res => {
is(
document.l10n.getAttributes(res.statusElement).args.status,
"Active",
"expecting status active"
);
}
);
info("Check parental controls enabled, TRR first");
await withConfiguration(
{
parentalControlsState: true,
trr_mode: 2,
wait_for_doh_status: "Not active (TRR_PARENTAL_CONTROL)",
},
async res => {
is(
document.l10n.getAttributes(res.statusElement).args.status,
"Not active (TRR_PARENTAL_CONTROL)",
"expecting status not active"
);
}
);
info("Check parental controls disabled, TRR only");
await withConfiguration(
{
parentalControlsState: false,
trr_mode: 3,
wait_for_doh_status: "Active",
},
async res => {
is(
document.l10n.getAttributes(res.statusElement).args.status,
"Active",
"expecting status active"
);
}
);
info("Check parental controls enabled, TRR only");
await withConfiguration(
{
parentalControlsState: true,
trr_mode: 3,
wait_for_doh_status: "Not active (TRR_PARENTAL_CONTROL)",
},
async res => {
is(
document.l10n.getAttributes(res.statusElement).args.status,
"Not active (TRR_PARENTAL_CONTROL)",
"expecting status not active"
);
}
);
await resetPrefs();
});
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]);
}
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
info(Date.now() - startTime + ": testWithProperties: tab now open");
let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
let uriTextbox = doc.getElementById("dohEnabledInputField");
let resolverMenulist = doc.getElementById("dohStrictResolverChoices");
let modePrefChangedPromise;
let uriPrefChangedPromise;
let disableHeuristicsPrefChangedPromise;
modeRadioGroup.scrollIntoView();
if (props.hasOwnProperty("expectedSelectedIndex")) {
await TestUtils.waitForCondition(
() => modeRadioGroup.selectedIndex === props.expectedSelectedIndex
);
is(
modeRadioGroup.selectedIndex,
props.expectedSelectedIndex,
"dohCategoryRadioGroup has expected selected index"
);
}
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) {
await clearEvents();
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"
);
let option = doc.getElementById(props.clickMode);
option.scrollIntoView();
let win = doc.ownerGlobal;
EventUtils.synthesizeMouseAtCenter(option, {}, win);
info(
`${Date.now() - startTime} : testWithProperties: clickMode=${
props.clickMode
}, mouse click synthesized`
);
let clickEvent = await getEvent("security.doh.settings", "mode_changed");
Assert.deepEqual(clickEvent, [
"security.doh.settings",
"mode_changed",
"button",
props.clickMode,
]);
}
if (props.hasOwnProperty("selectResolver")) {
await clearEvents();
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("command", { bubbles: true }));
info(
Date.now() -
startTime +
": testWithProperties: selectResolver, item value set and events dispatched"
);
let choiceEvent = await getEvent(
"security.doh.settings",
"provider_choice"
);
Assert.deepEqual(choiceEvent, [
"security.doh.settings",
"provider_choice",
"value",
props.selectResolver,
]);
}
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"
);
let win = doc.ownerGlobal;
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: 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")) {
if (props.expectedFinalUriPref) {
let uriPref = Services.prefs.getStringPref(TRR_URI_PREF);
is(
uriPref,
props.expectedFinalUriPref,
"uri pref ended up with the expected value"
);
} else {
ok(
!Services.prefs.prefHasUserValue(TRR_URI_PREF),
`uri pref ended up with the expected value (unset) got ${Services.prefs.getStringPref(
TRR_URI_PREF
)}`
);
}
}
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("expectedFinalCustomUriPref")) {
let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
is(
customUriPref,
props.expectedFinalCustomUriPref,
"custom_uri pref ended up with the expected value"
);
}
if (props.hasOwnProperty("expectedModeValue")) {
let modeValue = Services.prefs.getIntPref(TRR_MODE_PREF);
is(modeValue, props.expectedModeValue, "mode pref has expected value");
}
gBrowser.removeCurrentTab();
info(Date.now() - startTime + ": testWithProperties: fin");
}
add_task(async function default_values() {
let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
let uriPrefHasUserValue = Services.prefs.prefHasUserValue(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`
);
ok(
!uriPrefHasUserValue,
`Actual value of ${TRR_URI_PREF} matches expected default value (unset)`
);
is(
customUriPref,
defaultPrefValues[TRR_CUSTOM_URI_PREF],
`Actual value of ${TRR_CUSTOM_URI_PREF} matches expected default value`
);
});
const DEFAULT_OPTION_INDEX = 0;
const ENABLED_OPTION_INDEX = 1;
const STRICT_OPTION_INDEX = 2;
const OFF_OPTION_INDEX = 3;
let testVariations = [
// verify state with defaults
{
name: "default",
expectedModePref: 0,
expectedSelectedIndex: DEFAULT_OPTION_INDEX,
expectedUriValue: "",
},
// verify each of the modes maps to the correct checked state
{
name: "mode 0",
[TRR_MODE_PREF]: 0,
expectedSelectedIndex: DEFAULT_OPTION_INDEX,
},
{
name: "mode 1",
[TRR_MODE_PREF]: 1,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
{
name: "mode 2",
[TRR_MODE_PREF]: 2,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
},
{
name: "mode 2 and match default uri",
[TRR_MODE_PREF]: 2,
[TRR_URI_PREF]: "",
expectedSelectedIndex: ENABLED_OPTION_INDEX,
expectedFinalUriPref: DEFAULT_RESOLVER_VALUE,
},
{
name: "mode 3",
[TRR_MODE_PREF]: 3,
expectedSelectedIndex: STRICT_OPTION_INDEX,
},
{
name: "mode 3 and match default uri",
[TRR_URI_PREF]: "",
[TRR_MODE_PREF]: 3,
expectedSelectedIndex: STRICT_OPTION_INDEX,
expectedFinalUriPref: DEFAULT_RESOLVER_VALUE,
},
{
name: "mode 4",
[TRR_MODE_PREF]: 4,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
{
name: "mode 5",
[TRR_MODE_PREF]: 5,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
// verify an out of bounds mode value maps to the correct checked state
{
name: "mode out-of-bounds",
[TRR_MODE_PREF]: 77,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
{
name: "mode out-of-bounds-negative",
[TRR_MODE_PREF]: -1,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
// Changing mode 0 to mode 2 and 3
{
name: "back to mode 0",
[TRR_MODE_PREF]: 0,
expectedSelectedIndex: DEFAULT_OPTION_INDEX,
},
{
name: "mode 2 after mode 0",
[TRR_MODE_PREF]: 2,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
{
name: "back to mode 0_2",
[TRR_MODE_PREF]: 0,
expectedSelectedIndex: DEFAULT_OPTION_INDEX,
},
{
name: "mode 3 after mode 0",
[TRR_MODE_PREF]: 3,
expectedSelectedIndex: STRICT_OPTION_INDEX,
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
// verify the final URI of changing mode 5 to mode 2 and 3
{
name: "back to mode 5",
[TRR_MODE_PREF]: 5,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
{
name: "mode 2 after mode 5",
[TRR_MODE_PREF]: 2,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
{
name: "back to mode 5_2",
[TRR_MODE_PREF]: 5,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
{
name: "mode 3 after mode 5",
[TRR_MODE_PREF]: 3,
expectedSelectedIndex: STRICT_OPTION_INDEX,
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
// verify automatic heuristics states
{
name: "heuristics on and mode unset",
[TRR_MODE_PREF]: 0,
[ROLLOUT_ENABLED_PREF]: true,
expectedSelectedIndex: DEFAULT_OPTION_INDEX,
},
{
name: "heuristics on and mode set to 2",
[TRR_MODE_PREF]: 2,
[ROLLOUT_ENABLED_PREF]: true,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
},
{
name: "heuristics on but disabled, mode unset",
[TRR_MODE_PREF]: 5,
[ROLLOUT_ENABLED_PREF]: true,
expectedSelectedIndex: OFF_OPTION_INDEX,
},
{
name: "heuristics on but disabled, mode set to 2",
[TRR_MODE_PREF]: 2,
[ROLLOUT_ENABLED_PREF]: true,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
},
// verify picking each radio button option gives the right outcomes
{
name: "toggle mode on",
clickMode: "dohEnabledRadio",
expectedModeValue: 2,
expectedUriValue: "",
},
// TRR_URI_PREF should match the expectedFinalUriPref based on the changes made in Bug 1861285
{
name: "toggle mode on and auto default uri",
[TRR_URI_PREF]: "https://stub.com",
clickMode: "dohEnabledRadio",
expectedModeValue: 2,
expectedUriValue: "",
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
{
name: "toggle mode off",
[TRR_MODE_PREF]: 2,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
clickMode: "dohOffRadio",
expectedModePref: 5,
},
{
name: "toggle mode off when on due to heuristics",
[TRR_MODE_PREF]: 0,
[ROLLOUT_ENABLED_PREF]: true,
expectedSelectedIndex: DEFAULT_OPTION_INDEX,
clickMode: "dohOffRadio",
expectedModePref: 5,
expectedDisabledHeuristics: true,
},
// Test selecting non-default, non-custom TRR provider, NextDNS.
{
name: "Select NextDNS as TRR provider",
[TRR_MODE_PREF]: 2,
selectResolver: SECOND_RESOLVER_VALUE,
expectedFinalUriPref: SECOND_RESOLVER_VALUE,
},
// Test selecting non-default, non-custom TRR provider, NextDNS,
// with DoH not enabled. The provider selection should stick.
{
name: "Select NextDNS as TRR provider in mode 0",
[TRR_MODE_PREF]: 0,
selectResolver: SECOND_RESOLVER_VALUE,
expectedFinalUriPref: SECOND_RESOLVER_VALUE,
},
{
name: "return to default from NextDNS",
[TRR_MODE_PREF]: 2,
[TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
expectedResolverListValue: SECOND_RESOLVER_VALUE,
selectResolver: DEFAULT_RESOLVER_VALUE,
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
// Attempt to select NextDNS with DoH not enabled
// from mode 5 to 3
{
name: "Select NextDNS as TRR provider in mode 5",
[TRR_MODE_PREF]: 5,
selectResolver: SECOND_RESOLVER_VALUE,
expectedFinalUriPref: SECOND_RESOLVER_VALUE,
},
{
name: "return to default from NextDNS_2",
[TRR_MODE_PREF]: 2,
[TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
expectedResolverListValue: SECOND_RESOLVER_VALUE,
selectResolver: DEFAULT_RESOLVER_VALUE,
expectedFinalUriPref: FIRST_RESOLVER_VALUE,
},
{
name: "Select NextDNS as TRR provider in mode 5_2",
[TRR_MODE_PREF]: 5,
selectResolver: SECOND_RESOLVER_VALUE,
expectedFinalUriPref: SECOND_RESOLVER_VALUE,
},
{
name: "return to default from NextDNS_3",
[TRR_MODE_PREF]: 2,
[TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
expectedResolverListValue: SECOND_RESOLVER_VALUE,
selectResolver: DEFAULT_RESOLVER_VALUE,
expectedFinalUriPref: FIRST_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: 2,
expectedSelectedIndex: ENABLED_OPTION_INDEX,
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: "dohEnabledRadio",
selectResolver: "custom",
inputUriKeys: "https://custom.com",
expectedModePref: 2,
expectedFinalUriPref: "https://custom.com",
expectedFinalCustomUriPref: "https://custom.com",
},
{
name: "return to default from custom",
[TRR_MODE_PREF]: 2,
[TRR_URI_PREF]: "https://example.com",
[TRR_CUSTOM_URI_PREF]: "https://custom.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: " ",
expectedFinalCustomUriPref: "",
},
{
name: "empty default resolver list",
[TRR_MODE_PREF]: 2,
[TRR_URI_PREF]: "https://example.com",
[TRR_CUSTOM_URI_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() {
let startTime = Date.now();
info("starting test: " + props.name);
await testWithProperties(props, startTime);
await resetPrefs();
});
}
add_task(async function testRemoteSettingsEnable() {
let startTime = Date.now();
// Enable the rollout.
await DoHTestUtils.loadRemoteSettingsConfig({
providers: "example-1, example-2",
rolloutEnabled: true,
steeringEnabled: false,
steeringProviders: "",
autoDefaultEnabled: false,
autoDefaultProviders: "",
id: "global",
});
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
info(Date.now() - startTime + ": testWithProperties: tab now open");
let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
is(modeRadioGroup.value, "0", "expecting default mode");
let status = doc.getElementById("dohStatus");
await TestUtils.waitForCondition(
() => document.l10n.getAttributes(status).args.status == "Active"
);
is(
document.l10n.getAttributes(status).args.status,
"Active",
"expecting status active"
);
let provider = doc.getElementById("dohResolver");
is(
provider.hidden,
false,
"Provider should not be hidden when status is active"
);
await TestUtils.waitForCondition(
() =>
document.l10n.getAttributes(provider).args.name ==
DoHConfigController.currentConfig.providerList[0].UIName
);
is(
document.l10n.getAttributes(provider).args.name,
DoHConfigController.currentConfig.providerList[0].UIName,
"expecting the right provider name"
);
let option = doc.getElementById("dohEnabledRadio");
option.scrollIntoView();
let win = doc.ownerGlobal;
EventUtils.synthesizeMouseAtCenter(option, {}, win);
await TestUtils.waitForCondition(() =>
Services.prefs.prefHasUserValue("doh-rollout.disable-heuristics")
);
is(provider.hidden, false);
await TestUtils.waitForCondition(
() =>
document.l10n.getAttributes(provider).args.name ==
DoHConfigController.currentConfig.providerList[0].UIName
);
is(
document.l10n.getAttributes(provider).args.name,
DoHConfigController.currentConfig.providerList[0].UIName,
"expecting the right provider name"
);
is(
Services.prefs.getIntPref("network.trr.mode"),
Ci.nsIDNSService.MODE_TRRFIRST
);
option = doc.getElementById("dohOffRadio");
option.scrollIntoView();
win = doc.ownerGlobal;
EventUtils.synthesizeMouseAtCenter(option, {}, win);
await TestUtils.waitForCondition(() => status.innerHTML == "Status: Off");
is(
Services.prefs.getIntPref("network.trr.mode"),
Ci.nsIDNSService.MODE_TRROFF
);
is(provider.hidden, true, "Expecting provider to be hidden when DoH is off");
gBrowser.removeCurrentTab();
});
add_task(async function testEnterprisePolicy() {
async function withPolicy(policy, fn, preFn = () => {}) {
await resetPrefs();
PoliciesPrefTracker.start();
await EnterprisePolicyTesting.setupPolicyEngineWithJson(policy);
await preFn();
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
let resolverMenulist = doc.getElementById("dohEnabledResolverChoices");
let uriTextbox = doc.getElementById("dohEnabledInputField");
await fn({
modeRadioGroup,
resolverMenulist,
doc,
uriTextbox,
});
gBrowser.removeCurrentTab();
EnterprisePolicyTesting.resetRunOnceState();
PoliciesPrefTracker.stop();
}
info("Check that a locked policy does not allow any changes in the UI");
await withPolicy(
{
policies: {
DNSOverHTTPS: {
Enabled: true,
ExcludedDomains: ["examplelocked.com", "example.org"],
Locked: true,
},
},
},
async res => {
is(res.modeRadioGroup.disabled, true, "The mode menu should be locked.");
is(res.modeRadioGroup.value, "2", "Should be enabled");
is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
is(
res.uriTextbox.value,
"Custom URI should be set"
);
}
);
info("Check that an unlocked policy has editable fields in the dialog");
await withPolicy(
{
policies: {
DNSOverHTTPS: {
Enabled: true,
ExcludedDomains: ["example.com", "example.org"],
},
},
},
async res => {
is(
res.modeRadioGroup.disabled,
false,
"The mode menu should not be locked."
);
is(res.modeRadioGroup.value, "2", "Should be enabled");
is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
is(
res.uriTextbox.value,
"Expected custom resolver"
);
}
);
info("Check that a locked disabled policy disables the buttons");
await withPolicy(
{
policies: {
DNSOverHTTPS: {
Enabled: false,
ExcludedDomains: ["example.com", "example.org"],
Locked: true,
},
},
},
async res => {
is(res.modeRadioGroup.disabled, true, "The mode menu should be locked.");
is(res.modeRadioGroup.value, "5", "Should be disabled");
}
);
info("Check that an unlocked disabled policy has editable fields");
await withPolicy(
{
policies: {
DNSOverHTTPS: {
Enabled: false,
ExcludedDomains: ["example.com", "example.org"],
},
},
},
async res => {
is(
res.modeRadioGroup.disabled,
false,
"The mode menu should not be locked."
);
is(res.modeRadioGroup.value, "5", "Should be disabled");
}
);
info("Check that the remote settings config doesn't override the policy");
await withPolicy(
{
policies: {
DNSOverHTTPS: {
Enabled: true,
ExcludedDomains: ["example.com", "example.org"],
},
},
},
async res => {
is(
res.modeRadioGroup.disabled,
false,
"The mode menu should not be locked."
);
is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
is(
res.uriTextbox.value,
"Expected custom resolver"
);
},
async function runAfterSettingPolicy() {
await DoHTestUtils.loadRemoteSettingsConfig({
providers: "example-1, example-2",
rolloutEnabled: true,
steeringEnabled: false,
steeringProviders: "",
autoDefaultEnabled: false,
autoDefaultProviders: "",
id: "global",
});
}
);
});
add_task(async function clickWarnButton() {
Services.prefs.setBoolPref(
"network.trr_ui.show_fallback_warning_option",
true
);
await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
let doc = gBrowser.selectedBrowser.contentDocument;
await clearEvents();
let checkbox = doc.getElementById("dohWarnCheckbox1");
checkbox.click();
let event = await getEvent("security.doh.settings", "warn_checkbox");
Assert.deepEqual(event, [
"security.doh.settings",
"warn_checkbox",
"checkbox",
"true",
]);
Assert.equal(
Services.prefs.getBoolPref("network.trr.display_fallback_warning"),
true,
"Clicking the checkbox should change the pref"
);
checkbox.click();
event = await getEvent("security.doh.settings", "warn_checkbox");
Assert.deepEqual(event, [
"security.doh.settings",
"warn_checkbox",
"checkbox",
"false",
]);
Assert.equal(
Services.prefs.getBoolPref("network.trr.display_fallback_warning"),
false,
"Clicking the checkbox should change the pref"
);
gBrowser.removeCurrentTab();
});