"use strict";

const TEST_PAGE = TEST_DOMAIN + TEST_PATH + "page.html";
const TEST_REDIRECT_PAGE = TEST_DOMAIN + TEST_PATH + "redirect.sjs";
const TEST_TRACKING_PAGE = TEST_3RD_PARTY_DOMAIN + TEST_PATH + "page.html";
const TEST_TRACKING_REDIRECT_PAGE =
  TEST_3RD_PARTY_DOMAIN + TEST_PATH + "redirect.sjs";
const TEST_ANOTHER_TRACKING_REDIRECT_PAGE =
  TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS + TEST_PATH + "redirect.sjs";

const TEST_CASES = [
  // Tracker(Interacted) -> Non-Tracker
  {
    trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
    redirects: [TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
    expectedPermissionNumber: 1,
    expects: [
      [
        TEST_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.ALLOW_ACTION,
      ],
    ],
  },
  // Tracker(No interaction) -> Non-Tracker
  {
    trackersHasUserInteraction: [],
    redirects: [TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
    expectedPermissionNumber: 0,
    expects: [
      [
        TEST_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.UNKNOWN_ACTION,
      ],
    ],
  },
  // Non-Tracker -> Tracker(Interacted) -> Non-Tracker
  {
    trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
    redirects: [TEST_REDIRECT_PAGE, TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
    expectedPermissionNumber: 1,
    expects: [
      [
        TEST_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.ALLOW_ACTION,
      ],
    ],
  },
  // Tracker(Interacted) -> Tracker(Interacted) -> Tracker(Interacted)
  {
    trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
    redirects: [
      TEST_TRACKING_REDIRECT_PAGE,
      TEST_TRACKING_REDIRECT_PAGE,
      TEST_TRACKING_PAGE,
    ],
    expectedPermissionNumber: 0,
    expects: [
      [
        TEST_3RD_PARTY_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.UNKNOWN_ACTION,
      ],
    ],
  },
  // Tracker1(Interacted) -> Tracker2(Interacted) -> Non-Tracker
  {
    trackersHasUserInteraction: [
      TEST_3RD_PARTY_DOMAIN,
      TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS,
    ],
    redirects: [
      TEST_TRACKING_REDIRECT_PAGE,
      TEST_ANOTHER_TRACKING_REDIRECT_PAGE,
      TEST_PAGE,
    ],
    expectedPermissionNumber: 1,
    expects: [
      [
        TEST_DOMAIN,
        TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS,
        Ci.nsIPermissionManager.ALLOW_ACTION,
      ],
    ],
  },
  // Tracker1(Interacted) -> Non-Tracker -> Tracker2(No interaction) -> Non-Tracker
  {
    trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
    redirects: [
      TEST_TRACKING_REDIRECT_PAGE,
      TEST_REDIRECT_PAGE,
      TEST_ANOTHER_TRACKING_REDIRECT_PAGE,
      TEST_PAGE,
    ],
    expectedPermissionNumber: 1,
    expects: [
      [
        TEST_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.ALLOW_ACTION,
      ],
    ],
  },
  // Tracker1(Interacted) -> Non-Tracker -> Tracker2(Interacted) -> Non-Tracker
  // Note that the result is not quite correct in this case. We are supposed to
  // grant access to the least tracker instead of the first one. But, this is
  // the behavior how we act so far. We would fix this in another bug.
  {
    trackersHasUserInteraction: [
      TEST_3RD_PARTY_DOMAIN,
      TEST_ANOTHER_3RD_PARTY_DOMAIN_HTTPS,
    ],
    redirects: [
      TEST_TRACKING_REDIRECT_PAGE,
      TEST_REDIRECT_PAGE,
      TEST_ANOTHER_TRACKING_REDIRECT_PAGE,
      TEST_PAGE,
    ],
    expectedPermissionNumber: 1,
    expects: [
      [
        TEST_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.ALLOW_ACTION,
      ],
    ],
  },
  // Tracker(Interacted) -> Non-Tracker (heuristic disabled)
  {
    trackersHasUserInteraction: [TEST_3RD_PARTY_DOMAIN],
    redirects: [TEST_TRACKING_REDIRECT_PAGE, TEST_PAGE],
    expectedPermissionNumber: 0,
    expects: [
      [
        TEST_DOMAIN,
        TEST_3RD_PARTY_DOMAIN,
        Ci.nsIPermissionManager.UNKNOWN_ACTION,
      ],
    ],
    extraPrefs: [["privacy.antitracking.enableWebcompat", false]],
  },
];

async function interactWithSpecificTracker(aTracker) {
  let win = await BrowserTestUtils.openNewBrowserWindow();
  await BrowserTestUtils.withNewTab(
    { gBrowser: win.gBrowser, url: aTracker },
    async function (browser) {
      info("Let's interact with the tracker");

      await SpecialPowers.spawn(browser, [], async function () {
        SpecialPowers.wrap(content.document).userInteractionForTesting();
      });
    }
  );
  await BrowserTestUtils.closeWindow(win);
}

function getNumberOfStorageAccessPermissions() {
  let num = 0;
  for (let perm of Services.perms.all) {
    if (perm.type.startsWith("3rdPartyStorage^")) {
      num++;
    }
  }
  return num;
}

async function verifyStorageAccessPermission(aExpects) {
  for (let expect of aExpects) {
    let uri = Services.io.newURI(expect[0]);
    let principal = Services.scriptSecurityManager.createContentPrincipal(
      uri,
      {}
    );
    let access = Services.perms.testPermissionFromPrincipal(
      principal,
      `3rdPartyStorage^${expect[1].slice(0, -1)}`
    );

    is(access, expect[2], "The storage access is set correctly");
  }
}

add_setup(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["network.cookie.cookieBehavior", BEHAVIOR_REJECT_TRACKER],
      ["privacy.trackingprotection.annotate_channels", true],
      [
        "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
        "tracking.example.com,tracking.example.org",
      ],
      ["privacy.restrict3rdpartystorage.heuristic.redirect", true],
    ],
  });

  await UrlClassifierTestUtils.addTestTrackers();

  registerCleanupFunction(_ => {
    Services.perms.removeAll();
  });
});

add_task(async function testETPRedirectHeuristic() {
  info("Starting testing ETP redirect heuristic ...");

  for (const test of TEST_CASES) {
    let { extraPrefs } = test;
    if (extraPrefs) {
      await SpecialPowers.pushPrefEnv({
        set: extraPrefs,
      });
    }

    // First, clear all permissions.
    Services.perms.removeAll();

    for (const tracker of test.trackersHasUserInteraction) {
      info(`Interact with ${tracker} in top-level.`);
      await interactWithSpecificTracker(tracker);
    }

    info("Creating a new tab");
    let tab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE);
    gBrowser.selectedTab = tab;

    let browser = gBrowser.getBrowserForTab(tab);
    await BrowserTestUtils.browserLoaded(browser);

    info("Loading the tracking page and trigger the top-level redirect.");
    SpecialPowers.spawn(browser, [test.redirects], async redirects => {
      let link = content.document.createElement("a");
      link.appendChild(content.document.createTextNode("click me!"));
      link.href = redirects.shift() + "?" + redirects.join("|");
      content.document.body.appendChild(link);
      link.click();
    });

    let finalRedirectDest = test.redirects[test.redirects.length - 1];

    await BrowserTestUtils.browserLoaded(browser, false, finalRedirectDest);

    is(
      getNumberOfStorageAccessPermissions(),
      test.expectedPermissionNumber,
      "The number of storage permissions is correct."
    );

    await verifyStorageAccessPermission(test.expects);

    info("Removing the tab");
    BrowserTestUtils.removeTab(tab);

    if (extraPrefs) {
      await SpecialPowers.popPrefEnv();
    }
  }
});
