tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_privacy_dnsoverhttps.js (32287B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 requestLongerTimeout(4);
      7 
      8 const { EnterprisePolicyTesting, PoliciesPrefTracker } =
      9  ChromeUtils.importESModule(
     10    "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
     11  );
     12 
     13 ChromeUtils.defineESModuleGetters(this, {
     14  DoHConfigController: "moz-src:///toolkit/components/doh/DoHConfig.sys.mjs",
     15  DoHController: "moz-src:///toolkit/components/doh/DoHController.sys.mjs",
     16  DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs",
     17 });
     18 
     19 const { MockRegistrar } = ChromeUtils.importESModule(
     20  "resource://testing-common/MockRegistrar.sys.mjs"
     21 );
     22 const gDNSOverride = Cc[
     23  "@mozilla.org/network/native-dns-override;1"
     24 ].getService(Ci.nsINativeDNSResolverOverride);
     25 
     26 const TRR_MODE_PREF = "network.trr.mode";
     27 const TRR_URI_PREF = "network.trr.uri";
     28 const TRR_CUSTOM_URI_PREF = "network.trr.custom_uri";
     29 const ROLLOUT_ENABLED_PREF = "doh-rollout.enabled";
     30 const ROLLOUT_SELF_ENABLED_PREF = "doh-rollout.self-enabled";
     31 const HEURISTICS_DISABLED_PREF = "doh-rollout.disable-heuristics";
     32 const FIRST_RESOLVER_VALUE = DoHTestUtils.providers[0].uri;
     33 const SECOND_RESOLVER_VALUE = DoHTestUtils.providers[1].uri;
     34 const DEFAULT_RESOLVER_VALUE = FIRST_RESOLVER_VALUE;
     35 
     36 const defaultPrefValues = Object.freeze({
     37  [TRR_MODE_PREF]: 0,
     38  [TRR_CUSTOM_URI_PREF]: "",
     39 });
     40 
     41 // See bug 1741554. This test should not actually try to create a connection to
     42 // the real DoH endpoint. But a background request could do that while the test
     43 // is in progress, before we've actually disabled TRR, and would cause a crash
     44 // due to connecting to a non-local IP.
     45 // To prevent that we override the IP to a local address.
     46 gDNSOverride.addIPOverride("mozilla.cloudflare-dns.com", "127.0.0.1");
     47 
     48 async function clearEvents() {
     49  Services.telemetry.clearEvents();
     50  await TestUtils.waitForCondition(() => {
     51    let events = Services.telemetry.snapshotEvents(
     52      Ci.nsITelemetry.DATASET_ALL_CHANNELS,
     53      true
     54    ).parent;
     55    return !events || !events.length;
     56  });
     57 }
     58 
     59 async function getEvent(filter1, filter2) {
     60  let event = await TestUtils.waitForCondition(() => {
     61    let events = Services.telemetry.snapshotEvents(
     62      Ci.nsITelemetry.DATASET_ALL_CHANNELS,
     63      true
     64    ).parent;
     65    return events?.find(e => e[1] == filter1 && e[2] == filter2);
     66  }, "recorded telemetry for the load");
     67  event.shift();
     68  return event;
     69 }
     70 
     71 // Mock parental controls service in order to enable it
     72 let parentalControlsService = {
     73  parentalControlsEnabled: true,
     74  QueryInterface: ChromeUtils.generateQI(["nsIParentalControlsService"]),
     75 };
     76 let mockParentalControlsServiceCid = undefined;
     77 
     78 async function setMockParentalControlEnabled(aEnabled) {
     79  parentalControlsService.parentalControlsEnabled = aEnabled;
     80 }
     81 
     82 async function resetPrefs() {
     83  await DoHTestUtils.resetRemoteSettingsConfig();
     84  await DoHController._uninit();
     85  Services.prefs.clearUserPref(TRR_MODE_PREF);
     86  Services.prefs.clearUserPref(TRR_URI_PREF);
     87  Services.prefs.clearUserPref(TRR_CUSTOM_URI_PREF);
     88  Services.prefs.getChildList("doh-rollout.").forEach(pref => {
     89    Services.prefs.clearUserPref(pref);
     90  });
     91  // Clear out any telemetry events generated by DoHController so that we don't
     92  // confuse tests running after this one that are looking at those.
     93  Services.telemetry.clearEvents();
     94  await DoHController.init();
     95 }
     96 Services.prefs.setStringPref("network.trr.confirmationNS", "skip");
     97 
     98 registerCleanupFunction(async () => {
     99  await resetPrefs();
    100  Services.prefs.clearUserPref("network.trr.confirmationNS");
    101 
    102  if (mockParentalControlsServiceCid != undefined) {
    103    MockRegistrar.unregister(mockParentalControlsServiceCid);
    104    mockParentalControlsServiceCid = undefined;
    105    Services.dns.reloadParentalControlEnabled();
    106  }
    107 });
    108 
    109 add_setup(async function setup() {
    110  mockParentalControlsServiceCid = MockRegistrar.register(
    111    "@mozilla.org/parental-controls-service;1",
    112    parentalControlsService
    113  );
    114  Services.dns.reloadParentalControlEnabled();
    115 
    116  await SpecialPowers.pushPrefEnv({
    117    set: [["toolkit.telemetry.testing.overrideProductsCheck", true]],
    118  });
    119 
    120  await DoHTestUtils.resetRemoteSettingsConfig();
    121 
    122  gDNSOverride.addIPOverride("use-application-dns.net.", "4.1.1.1");
    123 
    124  setMockParentalControlEnabled(false);
    125 });
    126 
    127 function waitForPrefObserver(name) {
    128  return new Promise(resolve => {
    129    const observer = {
    130      observe(aSubject, aTopic, aData) {
    131        if (aData == name) {
    132          Services.prefs.removeObserver(name, observer);
    133          resolve();
    134        }
    135      },
    136    };
    137    Services.prefs.addObserver(name, observer);
    138  });
    139 }
    140 
    141 add_task(async function testParentalControls() {
    142  async function withConfiguration(configuration, fn) {
    143    info("testParentalControls");
    144 
    145    await resetPrefs();
    146    Services.prefs.setIntPref(TRR_MODE_PREF, configuration.trr_mode);
    147    await setMockParentalControlEnabled(configuration.parentalControlsState);
    148 
    149    await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
    150    let doc = gBrowser.selectedBrowser.contentDocument;
    151    let statusElement = doc.getElementById("dohStatus");
    152 
    153    await TestUtils.waitForCondition(() => {
    154      return (
    155        document.l10n.getAttributes(statusElement).args.status ==
    156        configuration.wait_for_doh_status
    157      );
    158    });
    159 
    160    await fn({
    161      statusElement,
    162    });
    163 
    164    gBrowser.removeCurrentTab();
    165    await setMockParentalControlEnabled(false);
    166  }
    167 
    168  info("Check parental controls disabled, TRR off");
    169  await withConfiguration(
    170    {
    171      parentalControlsState: false,
    172      trr_mode: 0,
    173      wait_for_doh_status: "Off",
    174    },
    175    async res => {
    176      is(
    177        document.l10n.getAttributes(res.statusElement).args.status,
    178        "Off",
    179        "expecting status off"
    180      );
    181    }
    182  );
    183 
    184  info("Check parental controls enabled, TRR off");
    185  await withConfiguration(
    186    {
    187      parentalControlsState: true,
    188      trr_mode: 0,
    189      wait_for_doh_status: "Off",
    190    },
    191    async res => {
    192      is(
    193        document.l10n.getAttributes(res.statusElement).args.status,
    194        "Off",
    195        "expecting status off"
    196      );
    197    }
    198  );
    199 
    200  // Enable the rollout.
    201  await DoHTestUtils.loadRemoteSettingsConfig({
    202    providers: "example",
    203    rolloutEnabled: true,
    204    steeringEnabled: false,
    205    steeringProviders: "",
    206    autoDefaultEnabled: false,
    207    autoDefaultProviders: "",
    208    id: "global",
    209  });
    210 
    211  info("Check parental controls disabled, TRR first");
    212  await withConfiguration(
    213    {
    214      parentalControlsState: false,
    215      trr_mode: 2,
    216      wait_for_doh_status: "Active",
    217    },
    218    async res => {
    219      is(
    220        document.l10n.getAttributes(res.statusElement).args.status,
    221        "Active",
    222        "expecting status active"
    223      );
    224    }
    225  );
    226 
    227  info("Check parental controls enabled, TRR first");
    228  await withConfiguration(
    229    {
    230      parentalControlsState: true,
    231      trr_mode: 2,
    232      wait_for_doh_status: "Not active (TRR_PARENTAL_CONTROL)",
    233    },
    234    async res => {
    235      is(
    236        document.l10n.getAttributes(res.statusElement).args.status,
    237        "Not active (TRR_PARENTAL_CONTROL)",
    238        "expecting status not active"
    239      );
    240    }
    241  );
    242 
    243  info("Check parental controls disabled, TRR only");
    244  await withConfiguration(
    245    {
    246      parentalControlsState: false,
    247      trr_mode: 3,
    248      wait_for_doh_status: "Active",
    249    },
    250    async res => {
    251      is(
    252        document.l10n.getAttributes(res.statusElement).args.status,
    253        "Active",
    254        "expecting status active"
    255      );
    256    }
    257  );
    258 
    259  info("Check parental controls enabled, TRR only");
    260  await withConfiguration(
    261    {
    262      parentalControlsState: true,
    263      trr_mode: 3,
    264      wait_for_doh_status: "Not active (TRR_PARENTAL_CONTROL)",
    265    },
    266    async res => {
    267      is(
    268        document.l10n.getAttributes(res.statusElement).args.status,
    269        "Not active (TRR_PARENTAL_CONTROL)",
    270        "expecting status not active"
    271      );
    272    }
    273  );
    274 
    275  await resetPrefs();
    276 });
    277 
    278 async function testWithProperties(props, startTime) {
    279  info(
    280    Date.now() -
    281      startTime +
    282      ": testWithProperties: testing with " +
    283      JSON.stringify(props)
    284  );
    285 
    286  // There are two different signals that the DoHController is ready, depending
    287  // on the config being tested. If we're setting the TRR mode pref, we should
    288  // expect the disable-heuristics pref to be set as the signal. Else, we can
    289  // expect the self-enabled pref as the signal.
    290  let rolloutReadyPromise;
    291  if (props.hasOwnProperty(TRR_MODE_PREF)) {
    292    if (
    293      [2, 3, 5].includes(props[TRR_MODE_PREF]) &&
    294      props.hasOwnProperty(ROLLOUT_ENABLED_PREF)
    295    ) {
    296      // Only initialize the promise if we're going to enable the rollout -
    297      // otherwise we will never await it, which could cause a leak if it doesn't
    298      // end up resolving.
    299      rolloutReadyPromise = waitForPrefObserver(HEURISTICS_DISABLED_PREF);
    300    }
    301    Services.prefs.setIntPref(TRR_MODE_PREF, props[TRR_MODE_PREF]);
    302  }
    303  if (props.hasOwnProperty(ROLLOUT_ENABLED_PREF)) {
    304    if (!rolloutReadyPromise) {
    305      rolloutReadyPromise = waitForPrefObserver(ROLLOUT_SELF_ENABLED_PREF);
    306    }
    307    Services.prefs.setBoolPref(
    308      ROLLOUT_ENABLED_PREF,
    309      props[ROLLOUT_ENABLED_PREF]
    310    );
    311    await rolloutReadyPromise;
    312  }
    313  if (props.hasOwnProperty(TRR_CUSTOM_URI_PREF)) {
    314    Services.prefs.setStringPref(
    315      TRR_CUSTOM_URI_PREF,
    316      props[TRR_CUSTOM_URI_PREF]
    317    );
    318  }
    319  if (props.hasOwnProperty(TRR_URI_PREF)) {
    320    Services.prefs.setStringPref(TRR_URI_PREF, props[TRR_URI_PREF]);
    321  }
    322 
    323  await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
    324  let doc = gBrowser.selectedBrowser.contentDocument;
    325 
    326  info(Date.now() - startTime + ": testWithProperties: tab now open");
    327  let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
    328  let uriTextbox = doc.getElementById("dohEnabledInputField");
    329  let resolverMenulist = doc.getElementById("dohStrictResolverChoices");
    330  let modePrefChangedPromise;
    331  let uriPrefChangedPromise;
    332  let disableHeuristicsPrefChangedPromise;
    333 
    334  modeRadioGroup.scrollIntoView();
    335 
    336  if (props.hasOwnProperty("expectedSelectedIndex")) {
    337    await TestUtils.waitForCondition(
    338      () => modeRadioGroup.selectedIndex === props.expectedSelectedIndex
    339    );
    340    is(
    341      modeRadioGroup.selectedIndex,
    342      props.expectedSelectedIndex,
    343      "dohCategoryRadioGroup has expected selected index"
    344    );
    345  }
    346  if (props.hasOwnProperty("expectedUriValue")) {
    347    await TestUtils.waitForCondition(
    348      () => uriTextbox.value === props.expectedUriValue
    349    );
    350    is(
    351      uriTextbox.value,
    352      props.expectedUriValue,
    353      "URI textbox has expected value"
    354    );
    355  }
    356  if (props.hasOwnProperty("expectedResolverListValue")) {
    357    await TestUtils.waitForCondition(
    358      () => resolverMenulist.value === props.expectedResolverListValue
    359    );
    360    is(
    361      resolverMenulist.value,
    362      props.expectedResolverListValue,
    363      "resolver menulist has expected value"
    364    );
    365  }
    366 
    367  if (props.clickMode) {
    368    await clearEvents();
    369    info(
    370      Date.now() -
    371        startTime +
    372        ": testWithProperties: clickMode, waiting for the pref observer"
    373    );
    374    modePrefChangedPromise = waitForPrefObserver(TRR_MODE_PREF);
    375    if (props.hasOwnProperty("expectedDisabledHeuristics")) {
    376      disableHeuristicsPrefChangedPromise = waitForPrefObserver(
    377        HEURISTICS_DISABLED_PREF
    378      );
    379    }
    380    info(
    381      Date.now() - startTime + ": testWithProperties: clickMode, pref changed"
    382    );
    383    let option = doc.getElementById(props.clickMode);
    384    option.scrollIntoView();
    385    let win = doc.ownerGlobal;
    386    EventUtils.synthesizeMouseAtCenter(option, {}, win);
    387    info(
    388      `${Date.now() - startTime} : testWithProperties: clickMode=${
    389        props.clickMode
    390      }, mouse click synthesized`
    391    );
    392    let clickEvent = await getEvent("security.doh.settings", "mode_changed");
    393    Assert.deepEqual(clickEvent, [
    394      "security.doh.settings",
    395      "mode_changed",
    396      "button",
    397      props.clickMode,
    398    ]);
    399  }
    400  if (props.hasOwnProperty("selectResolver")) {
    401    await clearEvents();
    402    info(
    403      Date.now() -
    404        startTime +
    405        ": testWithProperties: selectResolver, creating change event"
    406    );
    407    resolverMenulist.focus();
    408    resolverMenulist.value = props.selectResolver;
    409    resolverMenulist.dispatchEvent(new Event("input", { bubbles: true }));
    410    resolverMenulist.dispatchEvent(new Event("command", { bubbles: true }));
    411    info(
    412      Date.now() -
    413        startTime +
    414        ": testWithProperties: selectResolver, item value set and events dispatched"
    415    );
    416    let choiceEvent = await getEvent(
    417      "security.doh.settings",
    418      "provider_choice"
    419    );
    420    Assert.deepEqual(choiceEvent, [
    421      "security.doh.settings",
    422      "provider_choice",
    423      "value",
    424      props.selectResolver,
    425    ]);
    426  }
    427  if (props.hasOwnProperty("inputUriKeys")) {
    428    info(
    429      Date.now() -
    430        startTime +
    431        ": testWithProperties: inputUriKeys, waiting for the pref observer"
    432    );
    433    uriPrefChangedPromise = waitForPrefObserver(TRR_CUSTOM_URI_PREF);
    434    info(
    435      Date.now() -
    436        startTime +
    437        ": testWithProperties: inputUriKeys, pref changed, now enter the new value"
    438    );
    439    let win = doc.ownerGlobal;
    440    uriTextbox.focus();
    441    uriTextbox.value = props.inputUriKeys;
    442    uriTextbox.dispatchEvent(new win.Event("input", { bubbles: true }));
    443    uriTextbox.dispatchEvent(new win.Event("change", { bubbles: true }));
    444    info(
    445      Date.now() -
    446        startTime +
    447        ": testWithProperties: inputUriKeys, input and change events dispatched"
    448    );
    449  }
    450 
    451  info(
    452    Date.now() -
    453      startTime +
    454      ": testWithProperties: waiting for any of uri and mode prefs to change"
    455  );
    456  await Promise.all([
    457    uriPrefChangedPromise,
    458    modePrefChangedPromise,
    459    disableHeuristicsPrefChangedPromise,
    460  ]);
    461  info(Date.now() - startTime + ": testWithProperties: prefs changed");
    462 
    463  if (props.hasOwnProperty("expectedFinalUriPref")) {
    464    if (props.expectedFinalUriPref) {
    465      let uriPref = Services.prefs.getStringPref(TRR_URI_PREF);
    466      is(
    467        uriPref,
    468        props.expectedFinalUriPref,
    469        "uri pref ended up with the expected value"
    470      );
    471    } else {
    472      ok(
    473        !Services.prefs.prefHasUserValue(TRR_URI_PREF),
    474        `uri pref ended up with the expected value (unset) got ${Services.prefs.getStringPref(
    475          TRR_URI_PREF
    476        )}`
    477      );
    478    }
    479  }
    480 
    481  if (props.hasOwnProperty("expectedModePref")) {
    482    let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
    483    is(
    484      modePref,
    485      props.expectedModePref,
    486      "mode pref ended up with the expected value"
    487    );
    488  }
    489 
    490  if (props.hasOwnProperty("expectedDisabledHeuristics")) {
    491    let disabledHeuristicsPref = Services.prefs.getBoolPref(
    492      HEURISTICS_DISABLED_PREF
    493    );
    494    is(
    495      disabledHeuristicsPref,
    496      props.expectedDisabledHeuristics,
    497      "disable-heuristics pref ended up with the expected value"
    498    );
    499  }
    500 
    501  if (props.hasOwnProperty("expectedFinalCustomUriPref")) {
    502    let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
    503    is(
    504      customUriPref,
    505      props.expectedFinalCustomUriPref,
    506      "custom_uri pref ended up with the expected value"
    507    );
    508  }
    509 
    510  if (props.hasOwnProperty("expectedModeValue")) {
    511    let modeValue = Services.prefs.getIntPref(TRR_MODE_PREF);
    512    is(modeValue, props.expectedModeValue, "mode pref has expected value");
    513  }
    514 
    515  if (props.hasOwnProperty(ROLLOUT_ENABLED_PREF)) {
    516    Services.prefs.clearUserPref(ROLLOUT_ENABLED_PREF);
    517  }
    518 
    519  gBrowser.removeCurrentTab();
    520  info(Date.now() - startTime + ": testWithProperties: fin");
    521 }
    522 
    523 add_task(async function default_values() {
    524  let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF);
    525  let uriPrefHasUserValue = Services.prefs.prefHasUserValue(TRR_URI_PREF);
    526  let modePref = Services.prefs.getIntPref(TRR_MODE_PREF);
    527  is(
    528    modePref,
    529    defaultPrefValues[TRR_MODE_PREF],
    530    `Actual value of ${TRR_MODE_PREF} matches expected default value`
    531  );
    532  ok(
    533    !uriPrefHasUserValue,
    534    `Actual value of ${TRR_URI_PREF} matches expected default value (unset)`
    535  );
    536  is(
    537    customUriPref,
    538    defaultPrefValues[TRR_CUSTOM_URI_PREF],
    539    `Actual value of ${TRR_CUSTOM_URI_PREF} matches expected default value`
    540  );
    541 });
    542 
    543 const DEFAULT_OPTION_INDEX = 0;
    544 const ENABLED_OPTION_INDEX = 1;
    545 const STRICT_OPTION_INDEX = 2;
    546 const OFF_OPTION_INDEX = 3;
    547 
    548 let testVariations = [
    549  // verify state with defaults
    550  {
    551    name: "default",
    552    expectedModePref: 0,
    553    expectedSelectedIndex: DEFAULT_OPTION_INDEX,
    554    expectedUriValue: "",
    555  },
    556 
    557  // verify each of the modes maps to the correct checked state
    558  {
    559    name: "mode 0",
    560    [TRR_MODE_PREF]: 0,
    561    expectedSelectedIndex: DEFAULT_OPTION_INDEX,
    562  },
    563  {
    564    name: "mode 1",
    565    [TRR_MODE_PREF]: 1,
    566    expectedSelectedIndex: OFF_OPTION_INDEX,
    567  },
    568  {
    569    name: "mode 2",
    570    [TRR_MODE_PREF]: 2,
    571    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    572  },
    573  {
    574    name: "mode 2 and match default uri",
    575    [TRR_MODE_PREF]: 2,
    576    [TRR_URI_PREF]: "",
    577    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    578    expectedFinalUriPref: DEFAULT_RESOLVER_VALUE,
    579  },
    580  {
    581    name: "mode 3",
    582    [TRR_MODE_PREF]: 3,
    583    expectedSelectedIndex: STRICT_OPTION_INDEX,
    584  },
    585  {
    586    name: "mode 3 and match default uri",
    587    [TRR_URI_PREF]: "",
    588    [TRR_MODE_PREF]: 3,
    589    expectedSelectedIndex: STRICT_OPTION_INDEX,
    590    expectedFinalUriPref: DEFAULT_RESOLVER_VALUE,
    591  },
    592  {
    593    name: "mode 4",
    594    [TRR_MODE_PREF]: 4,
    595    expectedSelectedIndex: OFF_OPTION_INDEX,
    596  },
    597  {
    598    name: "mode 5",
    599    [TRR_MODE_PREF]: 5,
    600    expectedSelectedIndex: OFF_OPTION_INDEX,
    601  },
    602  // verify an out of bounds mode value maps to the correct checked state
    603  {
    604    name: "mode out-of-bounds",
    605    [TRR_MODE_PREF]: 77,
    606    expectedSelectedIndex: OFF_OPTION_INDEX,
    607  },
    608  {
    609    name: "mode out-of-bounds-negative",
    610    [TRR_MODE_PREF]: -1,
    611    expectedSelectedIndex: OFF_OPTION_INDEX,
    612  },
    613  // Changing mode 0 to mode 2 and 3
    614  {
    615    name: "back to mode 0",
    616    [TRR_MODE_PREF]: 0,
    617    expectedSelectedIndex: DEFAULT_OPTION_INDEX,
    618  },
    619  {
    620    name: "mode 2 after mode 0",
    621    [TRR_MODE_PREF]: 2,
    622    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    623    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    624  },
    625  {
    626    name: "back to mode 0_2",
    627    [TRR_MODE_PREF]: 0,
    628    expectedSelectedIndex: DEFAULT_OPTION_INDEX,
    629  },
    630  {
    631    name: "mode 3 after mode 0",
    632    [TRR_MODE_PREF]: 3,
    633    expectedSelectedIndex: STRICT_OPTION_INDEX,
    634    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    635  },
    636  // verify the final URI of changing mode 5 to mode 2 and 3
    637  {
    638    name: "back to mode 5",
    639    [TRR_MODE_PREF]: 5,
    640    expectedSelectedIndex: OFF_OPTION_INDEX,
    641  },
    642  {
    643    name: "mode 2 after mode 5",
    644    [TRR_MODE_PREF]: 2,
    645    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    646    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    647  },
    648  {
    649    name: "back to mode 5_2",
    650    [TRR_MODE_PREF]: 5,
    651    expectedSelectedIndex: OFF_OPTION_INDEX,
    652  },
    653  {
    654    name: "mode 3 after mode 5",
    655    [TRR_MODE_PREF]: 3,
    656    expectedSelectedIndex: STRICT_OPTION_INDEX,
    657    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    658  },
    659  // verify automatic heuristics states
    660  {
    661    name: "heuristics on and mode unset",
    662    [TRR_MODE_PREF]: 0,
    663    [ROLLOUT_ENABLED_PREF]: true,
    664    expectedSelectedIndex: DEFAULT_OPTION_INDEX,
    665  },
    666  {
    667    name: "heuristics on and mode set to 2",
    668    [TRR_MODE_PREF]: 2,
    669    [ROLLOUT_ENABLED_PREF]: true,
    670    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    671  },
    672  {
    673    name: "heuristics on but disabled, mode unset",
    674    [TRR_MODE_PREF]: 5,
    675    [ROLLOUT_ENABLED_PREF]: true,
    676    expectedSelectedIndex: OFF_OPTION_INDEX,
    677  },
    678  {
    679    name: "heuristics on but disabled, mode set to 2",
    680    [TRR_MODE_PREF]: 2,
    681    [ROLLOUT_ENABLED_PREF]: true,
    682    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    683  },
    684 
    685  // verify picking each radio button option gives the right outcomes
    686  {
    687    name: "toggle mode on",
    688    clickMode: "dohEnabledRadio",
    689    expectedModeValue: 2,
    690    expectedUriValue: "",
    691  },
    692  // TRR_URI_PREF should match the expectedFinalUriPref based on the changes made in Bug 1861285
    693  {
    694    name: "toggle mode on and auto default uri",
    695    [TRR_URI_PREF]: "https://stub.com",
    696    clickMode: "dohEnabledRadio",
    697    expectedModeValue: 2,
    698    expectedUriValue: "",
    699    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    700  },
    701  {
    702    name: "toggle mode off",
    703    [TRR_MODE_PREF]: 2,
    704    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    705    clickMode: "dohOffRadio",
    706    expectedModePref: 5,
    707  },
    708  {
    709    name: "toggle mode off when on due to heuristics",
    710    [TRR_MODE_PREF]: 0,
    711    [ROLLOUT_ENABLED_PREF]: true,
    712    expectedSelectedIndex: DEFAULT_OPTION_INDEX,
    713    clickMode: "dohOffRadio",
    714    expectedModePref: 5,
    715    expectedDisabledHeuristics: true,
    716  },
    717  // Test selecting non-default, non-custom TRR provider, NextDNS.
    718  {
    719    name: "Select NextDNS as TRR provider",
    720    [TRR_MODE_PREF]: 2,
    721    selectResolver: SECOND_RESOLVER_VALUE,
    722    expectedFinalUriPref: SECOND_RESOLVER_VALUE,
    723  },
    724  // Test selecting non-default, non-custom TRR provider, NextDNS,
    725  // with DoH not enabled. The provider selection should stick.
    726  {
    727    name: "Select NextDNS as TRR provider in mode 0",
    728    [TRR_MODE_PREF]: 0,
    729    selectResolver: SECOND_RESOLVER_VALUE,
    730    expectedFinalUriPref: SECOND_RESOLVER_VALUE,
    731  },
    732  {
    733    name: "return to default from NextDNS",
    734    [TRR_MODE_PREF]: 2,
    735    [TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
    736    expectedResolverListValue: SECOND_RESOLVER_VALUE,
    737    selectResolver: DEFAULT_RESOLVER_VALUE,
    738    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    739  },
    740  // Attempt to select NextDNS with DoH not enabled
    741  // from mode 5 to 3
    742  {
    743    name: "Select NextDNS as TRR provider in mode 5",
    744    [TRR_MODE_PREF]: 5,
    745    selectResolver: SECOND_RESOLVER_VALUE,
    746    expectedFinalUriPref: SECOND_RESOLVER_VALUE,
    747  },
    748  {
    749    name: "return to default from NextDNS_2",
    750    [TRR_MODE_PREF]: 2,
    751    [TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
    752    expectedResolverListValue: SECOND_RESOLVER_VALUE,
    753    selectResolver: DEFAULT_RESOLVER_VALUE,
    754    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    755  },
    756  {
    757    name: "Select NextDNS as TRR provider in mode 5_2",
    758    [TRR_MODE_PREF]: 5,
    759    selectResolver: SECOND_RESOLVER_VALUE,
    760    expectedFinalUriPref: SECOND_RESOLVER_VALUE,
    761  },
    762  {
    763    name: "return to default from NextDNS_3",
    764    [TRR_MODE_PREF]: 2,
    765    [TRR_URI_PREF]: SECOND_RESOLVER_VALUE,
    766    expectedResolverListValue: SECOND_RESOLVER_VALUE,
    767    selectResolver: DEFAULT_RESOLVER_VALUE,
    768    expectedFinalUriPref: FIRST_RESOLVER_VALUE,
    769  },
    770  // test that selecting Custom, when we have a TRR_CUSTOM_URI_PREF subsequently changes TRR_URI_PREF
    771  {
    772    name: "select custom with existing custom_uri pref value",
    773    [TRR_MODE_PREF]: 2,
    774    [TRR_CUSTOM_URI_PREF]: "https://example.com",
    775    expectedModeValue: 2,
    776    expectedSelectedIndex: ENABLED_OPTION_INDEX,
    777    selectResolver: "custom",
    778    expectedUriValue: "https://example.com",
    779    expectedFinalUriPref: "https://example.com",
    780    expectedFinalCustomUriPref: "https://example.com",
    781  },
    782  {
    783    name: "select custom and enter new custom_uri pref value",
    784    [TRR_URI_PREF]: "",
    785    [TRR_CUSTOM_URI_PREF]: "",
    786    clickMode: "dohEnabledRadio",
    787    selectResolver: "custom",
    788    inputUriKeys: "https://custom.com",
    789    expectedModePref: 2,
    790    expectedFinalUriPref: "https://custom.com",
    791    expectedFinalCustomUriPref: "https://custom.com",
    792  },
    793 
    794  {
    795    name: "return to default from custom",
    796    [TRR_MODE_PREF]: 2,
    797    [TRR_URI_PREF]: "https://example.com",
    798    [TRR_CUSTOM_URI_PREF]: "https://custom.com",
    799    expectedUriValue: "https://example.com",
    800    expectedResolverListValue: "custom",
    801    selectResolver: DEFAULT_RESOLVER_VALUE,
    802    expectedFinalUriPref: DEFAULT_RESOLVER_VALUE,
    803    expectedFinalCustomUriPref: "https://example.com",
    804  },
    805  {
    806    name: "clear the custom uri",
    807    [TRR_MODE_PREF]: 2,
    808    [TRR_URI_PREF]: "https://example.com",
    809    [TRR_CUSTOM_URI_PREF]: "https://example.com",
    810    expectedUriValue: "https://example.com",
    811    expectedResolverListValue: "custom",
    812    inputUriKeys: "",
    813    expectedFinalUriPref: " ",
    814    expectedFinalCustomUriPref: "",
    815  },
    816  {
    817    name: "empty default resolver list",
    818    [TRR_MODE_PREF]: 2,
    819    [TRR_URI_PREF]: "https://example.com",
    820    [TRR_CUSTOM_URI_PREF]: "",
    821    expectedUriValue: "https://example.com",
    822    expectedResolverListValue: "custom",
    823    expectedFinalUriPref: "https://example.com",
    824    expectedFinalCustomUriPref: "https://example.com",
    825  },
    826 ];
    827 
    828 for (let props of testVariations) {
    829  add_task(async function testVariation() {
    830    let startTime = Date.now();
    831    info("starting test: " + props.name);
    832    await testWithProperties(props, startTime);
    833    await resetPrefs();
    834  });
    835 }
    836 
    837 add_task(async function testRemoteSettingsEnable() {
    838  let startTime = Date.now();
    839  // Enable the rollout.
    840  await DoHTestUtils.loadRemoteSettingsConfig({
    841    providers: "example-1, example-2",
    842    rolloutEnabled: true,
    843    steeringEnabled: false,
    844    steeringProviders: "",
    845    autoDefaultEnabled: false,
    846    autoDefaultProviders: "",
    847    id: "global",
    848  });
    849 
    850  await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
    851  let doc = gBrowser.selectedBrowser.contentDocument;
    852 
    853  info(Date.now() - startTime + ": testWithProperties: tab now open");
    854  let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
    855 
    856  is(modeRadioGroup.value, "0", "expecting default mode");
    857 
    858  let status = doc.getElementById("dohStatus");
    859  await TestUtils.waitForCondition(
    860    () => document.l10n.getAttributes(status).args.status == "Active",
    861    "Waiting for remote settings to be processed"
    862  );
    863  is(
    864    document.l10n.getAttributes(status).args.status,
    865    "Active",
    866    "expecting status active"
    867  );
    868 
    869  let provider = doc.getElementById("dohResolver");
    870  is(
    871    provider.hidden,
    872    false,
    873    "Provider should not be hidden when status is active"
    874  );
    875  await TestUtils.waitForCondition(
    876    () =>
    877      document.l10n.getAttributes(provider).args.name ==
    878      DoHConfigController.currentConfig.providerList[0].UIName,
    879    "waiting for correct UI name"
    880  );
    881  is(
    882    document.l10n.getAttributes(provider).args.name,
    883    DoHConfigController.currentConfig.providerList[0].UIName,
    884    "expecting the right provider name"
    885  );
    886 
    887  let option = doc.getElementById("dohEnabledRadio");
    888  option.scrollIntoView();
    889  let win = doc.ownerGlobal;
    890  EventUtils.synthesizeMouseAtCenter(option, {}, win);
    891 
    892  await TestUtils.waitForCondition(
    893    () => Services.prefs.prefHasUserValue("doh-rollout.disable-heuristics"),
    894    "Waiting for disable-heuristics"
    895  );
    896  is(provider.hidden, false);
    897  await TestUtils.waitForCondition(
    898    () =>
    899      document.l10n.getAttributes(provider).args.name ==
    900      DoHConfigController.currentConfig.providerList[0].UIName,
    901    "waiting for correct UI name"
    902  );
    903  is(
    904    document.l10n.getAttributes(provider).args.name,
    905    DoHConfigController.currentConfig.providerList[0].UIName,
    906    "expecting the right provider name"
    907  );
    908  is(
    909    Services.prefs.getIntPref("network.trr.mode"),
    910    Ci.nsIDNSService.MODE_TRRFIRST
    911  );
    912 
    913  option = doc.getElementById("dohOffRadio");
    914  option.scrollIntoView();
    915  win = doc.ownerGlobal;
    916  EventUtils.synthesizeMouseAtCenter(option, {}, win);
    917  await TestUtils.waitForCondition(
    918    () => status.innerHTML == "Status: Off",
    919    "Waiting for Status OFF"
    920  );
    921  is(
    922    Services.prefs.getIntPref("network.trr.mode"),
    923    Ci.nsIDNSService.MODE_TRROFF
    924  );
    925  is(provider.hidden, true, "Expecting provider to be hidden when DoH is off");
    926 
    927  gBrowser.removeCurrentTab();
    928 
    929  await DoHTestUtils.loadRemoteSettingsConfig({
    930    providers: "",
    931    rolloutEnabled: false,
    932    steeringEnabled: false,
    933    steeringProviders: "",
    934    autoDefaultEnabled: false,
    935    autoDefaultProviders: "",
    936    id: "global",
    937  });
    938 });
    939 
    940 add_task(async function testEnterprisePolicy() {
    941  async function withPolicy(policy, fn, preFn = () => {}) {
    942    await resetPrefs();
    943    PoliciesPrefTracker.start();
    944    await EnterprisePolicyTesting.setupPolicyEngineWithJson(policy);
    945    await preFn();
    946 
    947    await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true });
    948    let doc = gBrowser.selectedBrowser.contentDocument;
    949 
    950    let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup");
    951    let resolverMenulist = doc.getElementById("dohEnabledResolverChoices");
    952    let uriTextbox = doc.getElementById("dohEnabledInputField");
    953 
    954    await fn({
    955      modeRadioGroup,
    956      resolverMenulist,
    957      doc,
    958      uriTextbox,
    959    });
    960 
    961    gBrowser.removeCurrentTab();
    962    // Set an empty policy before stopping the policy tracker
    963    await EnterprisePolicyTesting.setupPolicyEngineWithJson({});
    964    EnterprisePolicyTesting.resetRunOnceState();
    965    PoliciesPrefTracker.stop();
    966  }
    967 
    968  info("Check that a locked policy does not allow any changes in the UI");
    969  await withPolicy(
    970    {
    971      policies: {
    972        DNSOverHTTPS: {
    973          Enabled: true,
    974          ProviderURL: "https://examplelocked.com/provider",
    975          ExcludedDomains: ["examplelocked.com", "example.org"],
    976          Locked: true,
    977        },
    978      },
    979    },
    980    async res => {
    981      is(res.modeRadioGroup.disabled, true, "The mode menu should be locked.");
    982      is(res.modeRadioGroup.value, "2", "Should be enabled");
    983      is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
    984      is(
    985        res.uriTextbox.value,
    986        "https://examplelocked.com/provider",
    987        "Custom URI should be set"
    988      );
    989    }
    990  );
    991 
    992  info("Check that an unlocked policy has editable fields in the dialog");
    993  await withPolicy(
    994    {
    995      policies: {
    996        DNSOverHTTPS: {
    997          Enabled: true,
    998          ProviderURL: "https://example.com/provider",
    999          ExcludedDomains: ["example.com", "example.org"],
   1000        },
   1001      },
   1002    },
   1003    async res => {
   1004      is(
   1005        res.modeRadioGroup.disabled,
   1006        false,
   1007        "The mode menu should not be locked."
   1008      );
   1009      is(res.modeRadioGroup.value, "2", "Should be enabled");
   1010      is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
   1011      is(
   1012        res.uriTextbox.value,
   1013        "https://example.com/provider",
   1014        "Expected custom resolver"
   1015      );
   1016    }
   1017  );
   1018 
   1019  info("Check that a locked disabled policy disables the buttons");
   1020  await withPolicy(
   1021    {
   1022      policies: {
   1023        DNSOverHTTPS: {
   1024          Enabled: false,
   1025          ProviderURL: "https://example.com/provider",
   1026          ExcludedDomains: ["example.com", "example.org"],
   1027          Locked: true,
   1028        },
   1029      },
   1030    },
   1031    async res => {
   1032      is(res.modeRadioGroup.disabled, true, "The mode menu should be locked.");
   1033      is(res.modeRadioGroup.value, "5", "Should be disabled");
   1034    }
   1035  );
   1036 
   1037  info("Check that an unlocked disabled policy has editable fields");
   1038  await withPolicy(
   1039    {
   1040      policies: {
   1041        DNSOverHTTPS: {
   1042          Enabled: false,
   1043          ProviderURL: "https://example.com/provider",
   1044          ExcludedDomains: ["example.com", "example.org"],
   1045        },
   1046      },
   1047    },
   1048    async res => {
   1049      is(
   1050        res.modeRadioGroup.disabled,
   1051        false,
   1052        "The mode menu should not be locked."
   1053      );
   1054      is(res.modeRadioGroup.value, "5", "Should be disabled");
   1055    }
   1056  );
   1057 
   1058  info("Check that the remote settings config doesn't override the policy");
   1059  await withPolicy(
   1060    {
   1061      policies: {
   1062        DNSOverHTTPS: {
   1063          Enabled: true,
   1064          ProviderURL: "https://example.com/provider",
   1065          ExcludedDomains: ["example.com", "example.org"],
   1066        },
   1067      },
   1068    },
   1069    async res => {
   1070      is(
   1071        res.modeRadioGroup.disabled,
   1072        false,
   1073        "The mode menu should not be locked."
   1074      );
   1075      is(res.resolverMenulist.value, "custom", "Resolver list shows custom");
   1076      is(
   1077        res.uriTextbox.value,
   1078        "https://example.com/provider",
   1079        "Expected custom resolver"
   1080      );
   1081    },
   1082    async function runAfterSettingPolicy() {
   1083      await DoHTestUtils.loadRemoteSettingsConfig({
   1084        providers: "example-1, example-2",
   1085        rolloutEnabled: true,
   1086        steeringEnabled: false,
   1087        steeringProviders: "",
   1088        autoDefaultEnabled: false,
   1089        autoDefaultProviders: "",
   1090        id: "global",
   1091      });
   1092    }
   1093  );
   1094 });