"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionPreferencesManager:
    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
  Preferences: "resource://gre/modules/Preferences.sys.mjs",
});

const { createAppInfo, promiseShutdownManager, promiseStartupManager } =
  AddonTestUtils;

AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();

createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");

// Currently security.tls.version.min has a different default
// value in Nightly and Beta as opposed to Release builds.
const tlsMinPref = Services.prefs.getIntPref("security.tls.version.min");
if (tlsMinPref != 1 && tlsMinPref != 3) {
  ok(false, "This test expects security.tls.version.min set to 1 or 3.");
}
const tlsMinVer = tlsMinPref === 3 ? "TLSv1.2" : "TLSv1";
const READ_ONLY = true;

add_task(async function test_privacy() {
  // Create an object to hold the values to which we will initialize the prefs.
  const SETTINGS = {
    "network.networkPredictionEnabled": {
      "network.prefetch-next": true,
      // This pref starts with a numerical value and we need to use whatever the
      // default is or we encounter issues when the pref is reset during the test.
      "network.http.speculative-parallel-limit":
        ExtensionPreferencesManager.getDefaultValue(
          "network.http.speculative-parallel-limit"
        ),
      "network.dns.disablePrefetch": false,
    },
    "websites.hyperlinkAuditingEnabled": {
      "browser.send_pings": true,
    },
  };

  async function background() {
    browser.test.onMessage.addListener(async (msg, data, setting) => {
      // The second argument is the end of the api name,
      // e.g., "network.networkPredictionEnabled".
      let apiObj = setting.split(".").reduce((o, i) => o[i], browser.privacy);
      let settingData;
      switch (msg) {
        case "get":
          settingData = await apiObj.get(data);
          browser.test.sendMessage("gotData", settingData);
          break;

        case "set":
          await apiObj.set(data);
          settingData = await apiObj.get({});
          browser.test.sendMessage("afterSet", settingData);
          break;

        case "clear":
          await apiObj.clear(data);
          settingData = await apiObj.get({});
          browser.test.sendMessage("afterClear", settingData);
          break;
      }
    });
  }

  // Set prefs to our initial values.
  for (let setting in SETTINGS) {
    for (let pref in SETTINGS[setting]) {
      Preferences.set(pref, SETTINGS[setting][pref]);
    }
  }

  registerCleanupFunction(() => {
    // Reset the prefs.
    for (let setting in SETTINGS) {
      for (let pref in SETTINGS[setting]) {
        Preferences.reset(pref);
      }
    }
  });

  await promiseStartupManager();

  // Create an array of extensions to install.
  let testExtensions = [
    ExtensionTestUtils.loadExtension({
      background,
      manifest: {
        permissions: ["privacy"],
      },
      useAddonManager: "temporary",
    }),

    ExtensionTestUtils.loadExtension({
      background,
      manifest: {
        permissions: ["privacy"],
      },
      useAddonManager: "temporary",
    }),
  ];

  for (let extension of testExtensions) {
    await extension.startup();
  }

  for (let setting in SETTINGS) {
    testExtensions[0].sendMessage("get", {}, setting);
    let data = await testExtensions[0].awaitMessage("gotData");
    ok(data.value, "get returns expected value.");
    equal(
      data.levelOfControl,
      "controllable_by_this_extension",
      "get returns expected levelOfControl."
    );

    testExtensions[0].sendMessage("get", { incognito: true }, setting);
    data = await testExtensions[0].awaitMessage("gotData");
    ok(data.value, "get returns expected value with incognito.");
    equal(
      data.levelOfControl,
      "not_controllable",
      "get returns expected levelOfControl with incognito."
    );

    // Change the value to false.
    testExtensions[0].sendMessage("set", { value: false }, setting);
    data = await testExtensions[0].awaitMessage("afterSet");
    ok(!data.value, "get returns expected value after setting.");
    equal(
      data.levelOfControl,
      "controlled_by_this_extension",
      "get returns expected levelOfControl after setting."
    );

    // Verify the prefs have been set to match the "false" setting.
    for (let pref in SETTINGS[setting]) {
      let msg = `${pref} set correctly for ${setting}`;
      if (pref === "network.http.speculative-parallel-limit") {
        equal(Preferences.get(pref), 0, msg);
      } else {
        equal(Preferences.get(pref), !SETTINGS[setting][pref], msg);
      }
    }

    // Change the value with a newer extension.
    testExtensions[1].sendMessage("set", { value: true }, setting);
    data = await testExtensions[1].awaitMessage("afterSet");
    ok(
      data.value,
      "get returns expected value after setting via newer extension."
    );
    equal(
      data.levelOfControl,
      "controlled_by_this_extension",
      "get returns expected levelOfControl after setting."
    );

    // Verify the prefs have been set to match the "true" setting.
    for (let pref in SETTINGS[setting]) {
      let msg = `${pref} set correctly for ${setting}`;
      if (pref === "network.http.speculative-parallel-limit") {
        equal(
          Preferences.get(pref),
          ExtensionPreferencesManager.getDefaultValue(pref),
          msg
        );
      } else {
        equal(Preferences.get(pref), SETTINGS[setting][pref], msg);
      }
    }

    // Change the value with an older extension.
    testExtensions[0].sendMessage("set", { value: false }, setting);
    data = await testExtensions[0].awaitMessage("afterSet");
    ok(data.value, "Newer extension remains in control.");
    equal(
      data.levelOfControl,
      "controlled_by_other_extensions",
      "get returns expected levelOfControl when controlled by other."
    );

    // Clear the value of the newer extension.
    testExtensions[1].sendMessage("clear", {}, setting);
    data = await testExtensions[1].awaitMessage("afterClear");
    ok(!data.value, "Older extension gains control.");
    equal(
      data.levelOfControl,
      "controllable_by_this_extension",
      "Expected levelOfControl returned after clearing."
    );

    testExtensions[0].sendMessage("get", {}, setting);
    data = await testExtensions[0].awaitMessage("gotData");
    ok(!data.value, "Current, older extension has control.");
    equal(
      data.levelOfControl,
      "controlled_by_this_extension",
      "Expected levelOfControl returned after clearing."
    );

    // Set the value again with the newer extension.
    testExtensions[1].sendMessage("set", { value: true }, setting);
    data = await testExtensions[1].awaitMessage("afterSet");
    ok(
      data.value,
      "get returns expected value after setting via newer extension."
    );
    equal(
      data.levelOfControl,
      "controlled_by_this_extension",
      "get returns expected levelOfControl after setting."
    );

    // Unload the newer extension. Expect the older extension to regain control.
    await testExtensions[1].unload();
    testExtensions[0].sendMessage("get", {}, setting);
    data = await testExtensions[0].awaitMessage("gotData");
    ok(!data.value, "Older extension regained control.");
    equal(
      data.levelOfControl,
      "controlled_by_this_extension",
      "Expected levelOfControl returned after unloading."
    );

    // Reload the extension for the next iteration of the loop.
    testExtensions[1] = ExtensionTestUtils.loadExtension({
      background,
      manifest: {
        permissions: ["privacy"],
      },
      useAddonManager: "temporary",
    });
    await testExtensions[1].startup();

    // Clear the value of the older extension.
    testExtensions[0].sendMessage("clear", {}, setting);
    data = await testExtensions[0].awaitMessage("afterClear");
    ok(data.value, "Setting returns to original value when all are cleared.");
    equal(
      data.levelOfControl,
      "controllable_by_this_extension",
      "Expected levelOfControl returned after clearing."
    );

    // Verify that our initial values were restored.
    for (let pref in SETTINGS[setting]) {
      equal(
        Preferences.get(pref),
        SETTINGS[setting][pref],
        `${pref} was reset to its initial value.`
      );
    }
  }

  for (let extension of testExtensions) {
    await extension.unload();
  }

  await promiseShutdownManager();
});

add_task(async function test_privacy_other_prefs() {
  registerCleanupFunction(() => {
    Services.prefs.clearUserPref("security.tls.version.min");
    Services.prefs.clearUserPref("security.tls.version.max");
  });

  const cookieSvc = Ci.nsICookieService;

  // Create an object to hold the values to which we will initialize the prefs.
  const SETTINGS = {
    "network.webRTCIPHandlingPolicy": {
      "media.peerconnection.ice.default_address_only": false,
      "media.peerconnection.ice.no_host": false,
      "media.peerconnection.ice.proxy_only_if_behind_proxy": false,
      "media.peerconnection.ice.proxy_only": false,
    },
    "network.tlsVersionRestriction": {
      "security.tls.version.min": tlsMinPref,
      "security.tls.version.max": 4,
    },
    "network.peerConnectionEnabled": {
      "media.peerconnection.enabled": true,
    },
    "services.passwordSavingEnabled": {
      "signon.rememberSignons": true,
    },
    "websites.referrersEnabled": {
      "network.http.sendRefererHeader": 2,
    },
    "websites.resistFingerprinting": {
      "privacy.resistFingerprinting": true,
    },
    "websites.firstPartyIsolate": {
      "privacy.firstparty.isolate": false,
    },
    "websites.cookieConfig": {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_ACCEPT,
    },
  };

  let defaultPrefs = new Preferences({ defaultBranch: true });
  let defaultCookieBehavior = defaultPrefs.get("network.cookie.cookieBehavior");
  let defaultBehavior;
  switch (defaultCookieBehavior) {
    case cookieSvc.BEHAVIOR_ACCEPT:
      defaultBehavior = "allow_all";
      break;
    case cookieSvc.BEHAVIOR_REJECT_FOREIGN:
      defaultBehavior = "reject_third_party";
      break;
    case cookieSvc.BEHAVIOR_REJECT:
      defaultBehavior = "reject_all";
      break;
    case cookieSvc.BEHAVIOR_LIMIT_FOREIGN:
      defaultBehavior = "allow_visited";
      break;
    case cookieSvc.BEHAVIOR_REJECT_TRACKER:
      defaultBehavior = "reject_trackers";
      break;
    case cookieSvc.BEHAVIOR_PARTITION_FOREIGN:
      defaultBehavior = "reject_trackers_and_partition_foreign";
      break;
    default:
      ok(
        false,
        `Unexpected cookie behavior encountered: ${defaultCookieBehavior}`
      );
      break;
  }

  async function background() {
    let listeners = new Set([]);
    browser.test.onMessage.addListener(async (msg, data, setting, readOnly) => {
      // The second argument is the end of the api name,
      // e.g., "network.webRTCIPHandlingPolicy".
      let apiObj = setting.split(".").reduce((o, i) => o[i], browser.privacy);
      if (msg == "get") {
        browser.test.sendMessage("gettingData", await apiObj.get({}));
        return;
      }

      // Don't add more than one listener per apiName.  We leave the
      // listener to ensure we do not get more calls than we expect.
      if (!listeners.has(setting)) {
        apiObj.onChange.addListener(details => {
          browser.test.sendMessage("settingData", details);
        });
        listeners.add(setting);
      }
      try {
        await apiObj.set(data);
      } catch (e) {
        browser.test.sendMessage("settingThrowsException", {
          message: e.message,
        });
      }
      // Readonly settings will not trigger onChange, return the setting now.
      if (readOnly) {
        browser.test.sendMessage("settingData", await apiObj.get({}));
      }
    });
  }

  // Set prefs to our initial values.
  for (let setting in SETTINGS) {
    for (let pref in SETTINGS[setting]) {
      Preferences.set(pref, SETTINGS[setting][pref]);
    }
  }

  registerCleanupFunction(() => {
    // Reset the prefs.
    for (let setting in SETTINGS) {
      for (let pref in SETTINGS[setting]) {
        Preferences.reset(pref);
      }
    }
  });

  await promiseStartupManager();

  let extension = ExtensionTestUtils.loadExtension({
    background,
    manifest: {
      permissions: ["privacy"],
    },
    useAddonManager: "temporary",
  });

  await extension.startup();

  async function testSetting(setting, value, expected, expectedValue = value) {
    extension.sendMessage("set", { value: value }, setting);
    let data = await extension.awaitMessage("settingData");
    deepEqual(
      data.value,
      expectedValue,
      `Got expected result on setting ${setting} to ${uneval(value)}`
    );
    for (let pref in expected) {
      equal(
        Preferences.get(pref),
        expected[pref],
        `${pref} set correctly for ${expected[pref]}`
      );
    }
  }

  async function testSettingException(setting, value, expected) {
    extension.sendMessage("set", { value: value }, setting);
    let data = await extension.awaitMessage("settingThrowsException");
    equal(data.message, expected);
  }

  async function testGetting(getting, expected, expectedValue) {
    extension.sendMessage("get", null, getting);
    let data = await extension.awaitMessage("gettingData");
    deepEqual(
      data.value,
      expectedValue,
      `Got expected result on getting ${getting}`
    );
    for (let pref in expected) {
      equal(
        Preferences.get(pref),
        expected[pref],
        `${pref} get correctly for ${expected[pref]}`
      );
    }
  }

  await testSetting(
    "network.webRTCIPHandlingPolicy",
    "default_public_and_private_interfaces",
    {
      "media.peerconnection.ice.default_address_only": true,
      "media.peerconnection.ice.no_host": false,
      "media.peerconnection.ice.proxy_only_if_behind_proxy": false,
      "media.peerconnection.ice.proxy_only": false,
    }
  );
  await testSetting(
    "network.webRTCIPHandlingPolicy",
    "default_public_interface_only",
    {
      "media.peerconnection.ice.default_address_only": true,
      "media.peerconnection.ice.no_host": true,
      "media.peerconnection.ice.proxy_only_if_behind_proxy": false,
      "media.peerconnection.ice.proxy_only": false,
    }
  );
  await testSetting(
    "network.webRTCIPHandlingPolicy",
    "disable_non_proxied_udp",
    {
      "media.peerconnection.ice.default_address_only": true,
      "media.peerconnection.ice.no_host": true,
      "media.peerconnection.ice.proxy_only_if_behind_proxy": true,
      "media.peerconnection.ice.proxy_only": false,
    }
  );
  await testSetting("network.webRTCIPHandlingPolicy", "proxy_only", {
    "media.peerconnection.ice.default_address_only": false,
    "media.peerconnection.ice.no_host": false,
    "media.peerconnection.ice.proxy_only_if_behind_proxy": false,
    "media.peerconnection.ice.proxy_only": true,
  });
  await testSetting("network.webRTCIPHandlingPolicy", "default", {
    "media.peerconnection.ice.default_address_only": false,
    "media.peerconnection.ice.no_host": false,
    "media.peerconnection.ice.proxy_only_if_behind_proxy": false,
    "media.peerconnection.ice.proxy_only": false,
  });

  await testSetting("network.peerConnectionEnabled", false, {
    "media.peerconnection.enabled": false,
  });
  await testSetting("network.peerConnectionEnabled", true, {
    "media.peerconnection.enabled": true,
  });

  await testSetting("websites.referrersEnabled", false, {
    "network.http.sendRefererHeader": 0,
  });
  await testSetting("websites.referrersEnabled", true, {
    "network.http.sendRefererHeader": 2,
  });

  await testSetting("websites.resistFingerprinting", false, {
    "privacy.resistFingerprinting": false,
  });
  await testSetting("websites.resistFingerprinting", true, {
    "privacy.resistFingerprinting": true,
  });

  await testSetting("websites.trackingProtectionMode", "always", {
    "privacy.trackingprotection.enabled": true,
    "privacy.trackingprotection.pbmode.enabled": true,
  });
  await testSetting("websites.trackingProtectionMode", "never", {
    "privacy.trackingprotection.enabled": false,
    "privacy.trackingprotection.pbmode.enabled": false,
  });
  await testSetting("websites.trackingProtectionMode", "private_browsing", {
    "privacy.trackingprotection.enabled": false,
    "privacy.trackingprotection.pbmode.enabled": true,
  });

  await testSetting("services.passwordSavingEnabled", false, {
    "signon.rememberSignons": false,
  });
  await testSetting("services.passwordSavingEnabled", true, {
    "signon.rememberSignons": true,
  });

  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_third_party", nonPersistentCookies: true },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_REJECT_FOREIGN,
    },
    { behavior: "reject_third_party", nonPersistentCookies: false }
  );
  // A missing nonPersistentCookies property should default to false.
  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_third_party" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_REJECT_FOREIGN,
    },
    { behavior: "reject_third_party", nonPersistentCookies: false }
  );
  // A missing behavior property should reset the pref.
  await testSetting(
    "websites.cookieConfig",
    { nonPersistentCookies: true },
    {
      "network.cookie.cookieBehavior": defaultCookieBehavior,
    },
    { behavior: defaultBehavior, nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_all" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_REJECT,
    },
    { behavior: "reject_all", nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { behavior: "allow_visited" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_LIMIT_FOREIGN,
    },
    { behavior: "allow_visited", nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { behavior: "allow_all" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_ACCEPT,
    },
    { behavior: "allow_all", nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { nonPersistentCookies: true },
    {
      "network.cookie.cookieBehavior": defaultCookieBehavior,
    },
    { behavior: defaultBehavior, nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { nonPersistentCookies: false },
    {
      "network.cookie.cookieBehavior": defaultCookieBehavior,
    },
    { behavior: defaultBehavior, nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_trackers" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_REJECT_TRACKER,
    },
    { behavior: "reject_trackers", nonPersistentCookies: false }
  );
  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_trackers_and_partition_foreign" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_PARTITION_FOREIGN,
    },
    {
      behavior: "reject_trackers_and_partition_foreign",
      nonPersistentCookies: false,
    }
  );

  // 1. Can't enable FPI when cookie behavior is "reject_trackers_and_partition_foreign"
  await testSettingException(
    "websites.firstPartyIsolate",
    true,
    "Can't enable firstPartyIsolate when cookieBehavior is 'reject_trackers_and_partition_foreign'"
  );

  // 2. Change cookieConfig to reject_trackers should work normally.
  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_trackers" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_REJECT_TRACKER,
    },
    { behavior: "reject_trackers", nonPersistentCookies: false }
  );

  // 3. Enable FPI
  await testSetting("websites.firstPartyIsolate", true, {
    "privacy.firstparty.isolate": true,
  });

  // 4. When FPI is enabled, change setting to "reject_trackers_and_partition_foreign" is invalid
  await testSettingException(
    "websites.cookieConfig",
    { behavior: "reject_trackers_and_partition_foreign" },
    "Invalid cookieConfig 'reject_trackers_and_partition_foreign' when firstPartyIsolate is enabled"
  );

  // 5. Set conflict settings manually and check prefs.
  Preferences.set("network.cookie.cookieBehavior", 5);
  await testGetting(
    "websites.firstPartyIsolate",
    { "privacy.firstparty.isolate": true },
    true
  );
  await testGetting(
    "websites.cookieConfig",
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_PARTITION_FOREIGN,
    },
    {
      behavior: "reject_trackers_and_partition_foreign",
      nonPersistentCookies: false,
    }
  );

  // 6. It is okay to set current saved value.
  await testSetting("websites.firstPartyIsolate", true, {
    "privacy.firstparty.isolate": true,
  });
  await testSetting(
    "websites.cookieConfig",
    { behavior: "reject_trackers_and_partition_foreign" },
    {
      "network.cookie.cookieBehavior": cookieSvc.BEHAVIOR_PARTITION_FOREIGN,
    },
    {
      behavior: "reject_trackers_and_partition_foreign",
      nonPersistentCookies: false,
    }
  );

  await testSetting("websites.firstPartyIsolate", false, {
    "privacy.firstparty.isolate": false,
  });

  await testSetting(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.2",
      maximum: "TLSv1.3",
    },
    {
      "security.tls.version.min": 3,
      "security.tls.version.max": 4,
    }
  );

  // Single values
  await testSetting(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.3",
    },
    {
      "security.tls.version.min": 4,
      "security.tls.version.max": 4,
    },
    {
      minimum: "TLSv1.3",
      maximum: "TLSv1.3",
    }
  );

  // Single values
  await testSetting(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.3",
    },
    {
      "security.tls.version.min": 4,
      "security.tls.version.max": 4,
    },
    {
      minimum: "TLSv1.3",
      maximum: "TLSv1.3",
    }
  );

  // Invalid values.
  await testSettingException(
    "network.tlsVersionRestriction",
    {
      minimum: "invalid",
      maximum: "invalid",
    },
    "Setting TLS version invalid is not allowed for security reasons."
  );

  // Invalid values.
  await testSettingException(
    "network.tlsVersionRestriction",
    {
      minimum: "invalid2",
    },
    "Setting TLS version invalid2 is not allowed for security reasons."
  );

  // Invalid values.
  await testSettingException(
    "network.tlsVersionRestriction",
    {
      maximum: "invalid3",
    },
    "Setting TLS version invalid3 is not allowed for security reasons."
  );

  await testSetting(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.2",
    },
    {
      "security.tls.version.min": 3,
      "security.tls.version.max": 4,
    },
    {
      minimum: "TLSv1.2",
      maximum: "TLSv1.3",
    }
  );

  await testSetting(
    "network.tlsVersionRestriction",
    {
      maximum: "TLSv1.2",
    },
    {
      "security.tls.version.min": tlsMinPref,
      "security.tls.version.max": 3,
    },
    {
      minimum: tlsMinVer,
      maximum: "TLSv1.2",
    }
  );

  // Not supported version.
  if (tlsMinPref === 3) {
    await testSettingException(
      "network.tlsVersionRestriction",
      {
        minimum: "TLSv1",
      },
      "Setting TLS version TLSv1 is not allowed for security reasons."
    );

    await testSettingException(
      "network.tlsVersionRestriction",
      {
        minimum: "TLSv1.1",
      },
      "Setting TLS version TLSv1.1 is not allowed for security reasons."
    );

    await testSettingException(
      "network.tlsVersionRestriction",
      {
        maximum: "TLSv1",
      },
      "Setting TLS version TLSv1 is not allowed for security reasons."
    );

    await testSettingException(
      "network.tlsVersionRestriction",
      {
        maximum: "TLSv1.1",
      },
      "Setting TLS version TLSv1.1 is not allowed for security reasons."
    );
  }

  // Min vs Max
  await testSettingException(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.3",
      maximum: "TLSv1.2",
    },
    "Setting TLS min version grater than the max version is not allowed."
  );

  // Min vs Max (with default max)
  await testSetting(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.2",
      maximum: "TLSv1.2",
    },
    {
      "security.tls.version.min": 3,
      "security.tls.version.max": 3,
    }
  );
  await testSettingException(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.3",
    },
    "Setting TLS min version grater than the max version is not allowed."
  );

  // Max vs Min
  await testSetting(
    "network.tlsVersionRestriction",
    {
      minimum: "TLSv1.3",
      maximum: "TLSv1.3",
    },
    {
      "security.tls.version.min": 4,
      "security.tls.version.max": 4,
    }
  );
  await testSettingException(
    "network.tlsVersionRestriction",
    {
      maximum: "TLSv1.2",
    },
    "Setting TLS max version lower than the min version is not allowed."
  );

  // Empty value.
  await testSetting(
    "network.tlsVersionRestriction",
    {},
    {
      "security.tls.version.min": tlsMinPref,
      "security.tls.version.max": 4,
    },
    {
      minimum: tlsMinVer,
      maximum: "TLSv1.3",
    }
  );

  const GLOBAL_PRIVACY_CONTROL_PREF_NAME =
    "privacy.globalprivacycontrol.enabled";

  Preferences.set(GLOBAL_PRIVACY_CONTROL_PREF_NAME, false);
  await testGetting("network.globalPrivacyControl", {}, false);

  Preferences.set(GLOBAL_PRIVACY_CONTROL_PREF_NAME, true);
  await testGetting("network.globalPrivacyControl", {}, true);

  // trying to "set" should have no effect when readonly!
  extension.sendMessage(
    "set",
    { value: !Preferences.get(GLOBAL_PRIVACY_CONTROL_PREF_NAME) },
    "network.globalPrivacyControl",
    READ_ONLY
  );
  let readOnlyGPCData = await extension.awaitMessage("settingData");
  equal(
    readOnlyGPCData.value,
    Preferences.get(GLOBAL_PRIVACY_CONTROL_PREF_NAME),
    "extension cannot set globalPrivacyControl"
  );

  equal(Preferences.get(GLOBAL_PRIVACY_CONTROL_PREF_NAME), true);

  const HTTPS_ONLY_PREF_NAME = "dom.security.https_only_mode";
  const HTTPS_ONLY_PBM_PREF_NAME = "dom.security.https_only_mode_pbm";

  Preferences.set(HTTPS_ONLY_PREF_NAME, false);
  Preferences.set(HTTPS_ONLY_PBM_PREF_NAME, false);
  await testGetting("network.httpsOnlyMode", {}, "never");

  Preferences.set(HTTPS_ONLY_PREF_NAME, true);
  Preferences.set(HTTPS_ONLY_PBM_PREF_NAME, false);
  await testGetting("network.httpsOnlyMode", {}, "always");

  Preferences.set(HTTPS_ONLY_PREF_NAME, false);
  Preferences.set(HTTPS_ONLY_PBM_PREF_NAME, true);
  await testGetting("network.httpsOnlyMode", {}, "private_browsing");

  // Please note that if https_only_mode = true, then
  // https_only_mode_pbm has no effect.
  Preferences.set(HTTPS_ONLY_PREF_NAME, true);
  Preferences.set(HTTPS_ONLY_PBM_PREF_NAME, true);
  await testGetting("network.httpsOnlyMode", {}, "always");

  // trying to "set" should have no effect when readonly!
  extension.sendMessage(
    "set",
    { value: "never" },
    "network.httpsOnlyMode",
    READ_ONLY
  );
  let readOnlyData = await extension.awaitMessage("settingData");
  equal(readOnlyData.value, "always");

  equal(Preferences.get(HTTPS_ONLY_PREF_NAME), true);
  equal(Preferences.get(HTTPS_ONLY_PBM_PREF_NAME), true);

  await extension.unload();

  await promiseShutdownManager();
});

add_task(async function test_exceptions() {
  async function background() {
    await browser.test.assertRejects(
      browser.privacy.network.networkPredictionEnabled.set({
        value: true,
        scope: "regular_only",
      }),
      "Firefox does not support the regular_only settings scope.",
      "Expected rejection calling set with invalid scope."
    );

    await browser.test.assertRejects(
      browser.privacy.network.networkPredictionEnabled.clear({
        scope: "incognito_persistent",
      }),
      "Firefox does not support the incognito_persistent settings scope.",
      "Expected rejection calling clear with invalid scope."
    );

    browser.test.notifyPass("exceptionTests");
  }

  let extension = ExtensionTestUtils.loadExtension({
    background,
    manifest: {
      permissions: ["privacy"],
    },
  });

  await extension.startup();
  await extension.awaitFinish("exceptionTests");
  await extension.unload();
});
