1094 lines
32 KiB
JavaScript
1094 lines
32 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
requestLongerTimeout(4);
|
|
|
|
const { EnterprisePolicyTesting, PoliciesPrefTracker } =
|
|
ChromeUtils.importESModule(
|
|
"resource://testing-common/EnterprisePolicyTesting.sys.mjs"
|
|
);
|
|
|
|
ChromeUtils.defineESModuleGetters(this, {
|
|
DoHConfigController: "resource://gre/modules/DoHConfig.sys.mjs",
|
|
DoHController: "resource://gre/modules/DoHController.sys.mjs",
|
|
DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs",
|
|
});
|
|
|
|
const { MockRegistrar } = ChromeUtils.importESModule(
|
|
"resource://testing-common/MockRegistrar.sys.mjs"
|
|
);
|
|
const gDNSOverride = Cc[
|
|
"@mozilla.org/network/native-dns-override;1"
|
|
].getService(Ci.nsINativeDNSResolverOverride);
|
|
|
|
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.
|
|
gDNSOverride.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;
|
|
}
|
|
|
|
// Mock parental controls service in order to enable it
|
|
let parentalControlsService = {
|
|
parentalControlsEnabled: true,
|
|
QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
|
|
};
|
|
let mockParentalControlsServiceCid = undefined;
|
|
|
|
async function setMockParentalControlEnabled(aEnabled) {
|
|
parentalControlsService.parentalControlsEnabled = aEnabled;
|
|
}
|
|
|
|
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");
|
|
|
|
if (mockParentalControlsServiceCid != undefined) {
|
|
MockRegistrar.unregister(mockParentalControlsServiceCid);
|
|
mockParentalControlsServiceCid = undefined;
|
|
Services.dns.reloadParentalControlEnabled();
|
|
}
|
|
});
|
|
|
|
add_setup(async function setup() {
|
|
mockParentalControlsServiceCid = MockRegistrar.register(
|
|
"@mozilla.org/parental-controls-service;1",
|
|
parentalControlsService
|
|
);
|
|
Services.dns.reloadParentalControlEnabled();
|
|
|
|
await SpecialPowers.pushPrefEnv({
|
|
set: [["toolkit.telemetry.testing.overrideProductsCheck", true]],
|
|
});
|
|
|
|
await DoHTestUtils.resetRemoteSettingsConfig();
|
|
|
|
gDNSOverride.addIPOverride("use-application-dns.net.", "4.1.1.1");
|
|
|
|
setMockParentalControlEnabled(false);
|
|
});
|
|
|
|
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);
|
|
});
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
if (props.hasOwnProperty(ROLLOUT_ENABLED_PREF)) {
|
|
Services.prefs.clearUserPref(ROLLOUT_ENABLED_PREF);
|
|
}
|
|
|
|
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",
|
|
"Waiting for remote settings to be processed"
|
|
);
|
|
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,
|
|
"waiting for correct UI name"
|
|
);
|
|
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"),
|
|
"Waiting for disable-heuristics"
|
|
);
|
|
is(provider.hidden, false);
|
|
await TestUtils.waitForCondition(
|
|
() =>
|
|
document.l10n.getAttributes(provider).args.name ==
|
|
DoHConfigController.currentConfig.providerList[0].UIName,
|
|
"waiting for correct UI name"
|
|
);
|
|
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",
|
|
"Waiting for 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();
|
|
|
|
await DoHTestUtils.loadRemoteSettingsConfig({
|
|
providers: "",
|
|
rolloutEnabled: false,
|
|
steeringEnabled: false,
|
|
steeringProviders: "",
|
|
autoDefaultEnabled: false,
|
|
autoDefaultProviders: "",
|
|
id: "global",
|
|
});
|
|
});
|
|
|
|
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();
|
|
// Set an empty policy before stopping the policy tracker
|
|
await EnterprisePolicyTesting.setupPolicyEngineWithJson({});
|
|
EnterprisePolicyTesting.resetRunOnceState();
|
|
PoliciesPrefTracker.stop();
|
|
}
|
|
|
|
info("Check that a locked policy does not allow any changes in the UI");
|
|
await withPolicy(
|
|
{
|
|
policies: {
|
|
DNSOverHTTPS: {
|
|
Enabled: true,
|
|
ProviderURL: "https://examplelocked.com/provider",
|
|
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,
|
|
"https://examplelocked.com/provider",
|
|
"Custom URI should be set"
|
|
);
|
|
}
|
|
);
|
|
|
|
info("Check that an unlocked policy has editable fields in the dialog");
|
|
await withPolicy(
|
|
{
|
|
policies: {
|
|
DNSOverHTTPS: {
|
|
Enabled: true,
|
|
ProviderURL: "https://example.com/provider",
|
|
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,
|
|
"https://example.com/provider",
|
|
"Expected custom resolver"
|
|
);
|
|
}
|
|
);
|
|
|
|
info("Check that a locked disabled policy disables the buttons");
|
|
await withPolicy(
|
|
{
|
|
policies: {
|
|
DNSOverHTTPS: {
|
|
Enabled: false,
|
|
ProviderURL: "https://example.com/provider",
|
|
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,
|
|
ProviderURL: "https://example.com/provider",
|
|
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,
|
|
ProviderURL: "https://example.com/provider",
|
|
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,
|
|
"https://example.com/provider",
|
|
"Expected custom resolver"
|
|
);
|
|
},
|
|
async function runAfterSettingPolicy() {
|
|
await DoHTestUtils.loadRemoteSettingsConfig({
|
|
providers: "example-1, example-2",
|
|
rolloutEnabled: true,
|
|
steeringEnabled: false,
|
|
steeringProviders: "",
|
|
autoDefaultEnabled: false,
|
|
autoDefaultProviders: "",
|
|
id: "global",
|
|
});
|
|
}
|
|
);
|
|
});
|