forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			844 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			844 lines
		
	
	
	
		
			25 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:///modules/DoHConfig.sys.mjs",
 | 
						|
  DoHController: "resource:///modules/DoHController.sys.mjs",
 | 
						|
  DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
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);
 | 
						|
  });
 | 
						|
}
 | 
						|
 | 
						|
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,
 | 
						|
    expectedFinalUriPref: "",
 | 
						|
  },
 | 
						|
  {
 | 
						|
    name: "mode 3",
 | 
						|
    [TRR_MODE_PREF]: 3,
 | 
						|
    expectedSelectedIndex: STRICT_OPTION_INDEX,
 | 
						|
    expectedFinalUriPref: "",
 | 
						|
  },
 | 
						|
  {
 | 
						|
    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,
 | 
						|
  },
 | 
						|
 | 
						|
  // 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: "",
 | 
						|
    expectedFinalUriPref: "",
 | 
						|
  },
 | 
						|
  {
 | 
						|
    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,
 | 
						|
  },
 | 
						|
  // 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,
 | 
						|
          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",
 | 
						|
      });
 | 
						|
    }
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
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();
 | 
						|
});
 |