/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/* eslint-disable mozilla/no-arbitrary-setTimeout */

"use strict";

const COOKIE_PAGE =
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/cookiePage.html";
const CONTAINER_PAGE =
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  "http://not-tracking.example.com/browser/browser/base/content/test/protectionsUI/containerPage.html";

const TPC_PREF = "network.cookie.cookieBehavior";

add_setup(async function () {
  await UrlClassifierTestUtils.addTestTrackers();

  registerCleanupFunction(() => {
    UrlClassifierTestUtils.cleanupTestTrackers();
  });
});

/*
 * Accepts an array containing 6 elements that identify the testcase:
 * [0] - boolean indicating whether trackers are blocked.
 * [1] - boolean indicating whether third party cookies are blocked.
 * [2] - boolean indicating whether first party cookies are blocked.
 * [3] - integer indicating number of expected content blocking events.
 * [4] - integer indicating number of expected subview list headers.
 * [5] - integer indicating number of expected cookie list items.
 * [6] - integer indicating number of expected cookie list items
 *       after loading a cookie-setting third party URL in an iframe
 * [7] - integer indicating number of expected cookie list items
 *       after loading a cookie-setting first party URL in an iframe
 */
async function assertSitesListed(testCase) {
  let sitesListedTestCases = [
    [true, false, false, 4, 1, 1, 1, 1],
    [true, true, false, 5, 1, 1, 2, 2],
    [true, true, true, 6, 2, 2, 3, 3],
    [false, false, false, 3, 1, 1, 1, 1],
  ];
  let [
    trackersBlocked,
    thirdPartyBlocked,
    firstPartyBlocked,
    contentBlockingEventCount,
    listHeaderCount,
    cookieItemsCount1,
    cookieItemsCount2,
    cookieItemsCount3,
  ] = sitesListedTestCases[testCase];
  let promise = BrowserTestUtils.openNewForegroundTab({
    url: COOKIE_PAGE,
    gBrowser,
  });
  let [tab] = await Promise.all([
    promise,
    waitForContentBlockingEvent(contentBlockingEventCount),
  ]);
  let browser = tab.linkedBrowser;

  await openProtectionsPanel();

  let categoryItem = document.getElementById(
    "protections-popup-category-cookies"
  );

  // Explicitly waiting for the category item becoming visible.
  await TestUtils.waitForCondition(() => {
    return BrowserTestUtils.isVisible(categoryItem);
  });

  ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
  let cookiesView = document.getElementById("protections-popup-cookiesView");
  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
  categoryItem.click();
  await viewShown;

  ok(true, "Cookies view was shown");

  let listHeaders = cookiesView.querySelectorAll(
    ".protections-popup-cookiesView-list-header"
  );
  is(
    listHeaders.length,
    listHeaderCount,
    `We have ${listHeaderCount} list headers.`
  );
  if (listHeaderCount == 1) {
    ok(
      !BrowserTestUtils.isVisible(listHeaders[0]),
      "Only one header, should be hidden"
    );
  } else {
    for (let header of listHeaders) {
      ok(
        BrowserTestUtils.isVisible(header),
        "Multiple list headers - all should be visible."
      );
    }
  }

  let emptyLabels = cookiesView.querySelectorAll(
    ".protections-popup-empty-label"
  );
  is(emptyLabels.length, 0, `We have no empty labels`);

  let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
  is(
    listItems.length,
    cookieItemsCount1,
    `We have ${cookieItemsCount1} cookies in the list`
  );

  if (trackersBlocked) {
    let trackerTestItem;
    for (let item of listItems) {
      let label = item.querySelector(".protections-popup-list-host-label");
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      if (label.value == "http://trackertest.org") {
        trackerTestItem = item;
        break;
      }
    }
    ok(trackerTestItem, "Has an item for trackertest.org");
    ok(BrowserTestUtils.isVisible(trackerTestItem), "List item is visible");
  }

  if (firstPartyBlocked) {
    let notTrackingExampleItem;
    for (let item of listItems) {
      let label = item.querySelector(".protections-popup-list-host-label");
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      if (label.value == "http://not-tracking.example.com") {
        notTrackingExampleItem = item;
        break;
      }
    }
    ok(notTrackingExampleItem, "Has an item for not-tracking.example.com");
    ok(
      BrowserTestUtils.isVisible(notTrackingExampleItem),
      "List item is visible"
    );
  }
  let mainView = document.getElementById("protections-popup-mainView");
  viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
  let backButton = cookiesView.querySelector(".subviewbutton-back");
  backButton.click();
  await viewShown;

  ok(true, "Main view was shown");

  let change = waitForContentBlockingEvent();
  let timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));

  await SpecialPowers.spawn(browser, [], function () {
    content.postMessage("third-party-cookie", "*");
  });

  let result = await Promise.race([change, timeoutPromise]);
  is(result, undefined, "No contentBlockingEvent events should be received");

  viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
  categoryItem.click();
  await viewShown;

  ok(true, "Cookies view was shown");

  emptyLabels = cookiesView.querySelectorAll(".protections-popup-empty-label");
  is(emptyLabels.length, 0, `We have no empty labels`);

  listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
  is(
    listItems.length,
    cookieItemsCount2,
    `We have ${cookieItemsCount2} cookies in the list`
  );

  if (thirdPartyBlocked) {
    let test1ExampleItem;
    for (let item of listItems) {
      let label = item.querySelector(".protections-popup-list-host-label");
      if (label.value == "https://test1.example.org") {
        test1ExampleItem = item;
        break;
      }
    }
    ok(test1ExampleItem, "Has an item for test1.example.org");
    ok(BrowserTestUtils.isVisible(test1ExampleItem), "List item is visible");
  }

  if (trackersBlocked || thirdPartyBlocked || firstPartyBlocked) {
    let trackerTestItem;
    for (let item of listItems) {
      let label = item.querySelector(".protections-popup-list-host-label");
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      if (label.value == "http://trackertest.org") {
        trackerTestItem = item;
        break;
      }
    }
    ok(trackerTestItem, "List item should exist for http://trackertest.org");
    ok(BrowserTestUtils.isVisible(trackerTestItem), "List item is visible");
  }

  viewShown = BrowserTestUtils.waitForEvent(mainView, "ViewShown");
  backButton.click();
  await viewShown;

  ok(true, "Main view was shown");

  change = waitForSecurityChange();
  timeoutPromise = new Promise(resolve => setTimeout(resolve, 1000));

  await SpecialPowers.spawn(browser, [], function () {
    content.postMessage("first-party-cookie", "*");
  });

  result = await Promise.race([change, timeoutPromise]);
  is(result, undefined, "No securityChange events should be received");

  viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
  categoryItem.click();
  await viewShown;

  ok(true, "Cookies view was shown");

  emptyLabels = cookiesView.querySelectorAll(".protections-popup-empty-label");
  is(emptyLabels.length, 0, "We have no empty labels");

  listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
  is(
    listItems.length,
    cookieItemsCount3,
    `We have ${cookieItemsCount3} cookies in the list`
  );

  if (firstPartyBlocked) {
    let notTrackingExampleItem;
    for (let item of listItems) {
      let label = item.querySelector(".protections-popup-list-host-label");
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      if (label.value == "http://not-tracking.example.com") {
        notTrackingExampleItem = item;
        break;
      }
    }
    ok(notTrackingExampleItem, "Has an item for not-tracking.example.com");
    ok(
      BrowserTestUtils.isVisible(notTrackingExampleItem),
      "List item is visible"
    );
  }

  BrowserTestUtils.removeTab(tab);
}

add_task(async function testCookiesSubView() {
  info("Testing cookies subview with reject tracking cookies.");
  Services.prefs.setIntPref(
    TPC_PREF,
    Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
  );
  let testCaseIndex = 0;
  await assertSitesListed(testCaseIndex++);
  info("Testing cookies subview with reject third party cookies.");
  Services.prefs.setIntPref(
    TPC_PREF,
    Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN
  );
  await assertSitesListed(testCaseIndex++);
  info("Testing cookies subview with reject all cookies.");
  Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_REJECT);
  await assertSitesListed(testCaseIndex++);
  info("Testing cookies subview with accept all cookies.");
  Services.prefs.setIntPref(TPC_PREF, Ci.nsICookieService.BEHAVIOR_ACCEPT);
  await assertSitesListed(testCaseIndex++);

  Services.prefs.clearUserPref(TPC_PREF);
});

add_task(async function testCookiesSubViewAllowed() {
  Services.prefs.setIntPref(
    TPC_PREF,
    Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
  );
  let principal =
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      "http://trackertest.org/"
    );
  Services.perms.addFromPrincipal(
    principal,
    "cookie",
    Services.perms.ALLOW_ACTION
  );

  let promise = BrowserTestUtils.openNewForegroundTab({
    url: COOKIE_PAGE,
    gBrowser,
  });
  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]);

  await openProtectionsPanel();

  let categoryItem = document.getElementById(
    "protections-popup-category-cookies"
  );

  // Explicitly waiting for the category item becoming visible.
  await TestUtils.waitForCondition(() => {
    return BrowserTestUtils.isVisible(categoryItem);
  });

  ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
  let cookiesView = document.getElementById("protections-popup-cookiesView");
  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
  categoryItem.click();
  await viewShown;

  ok(true, "Cookies view was shown");

  let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
  is(listItems.length, 1, "We have 1 cookie in the list");

  let listItem = listItems[0];
  let label = listItem.querySelector(".protections-popup-list-host-label");
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  is(label.value, "http://trackertest.org", "has an item for trackertest.org");
  ok(BrowserTestUtils.isVisible(listItem), "list item is visible");
  ok(
    listItem.classList.contains("allowed"),
    "indicates whether the cookie was blocked or allowed"
  );

  let stateLabel = listItem.querySelector(
    ".protections-popup-list-state-label"
  );
  ok(stateLabel, "List item has a state label");
  ok(BrowserTestUtils.isVisible(stateLabel), "State label is visible");
  is(
    stateLabel.getAttribute("data-l10n-id"),
    "content-blocking-cookies-view-allowed-label",
    "State label has correct text"
  );

  let button = listItem.querySelector(
    ".permission-popup-permission-remove-button"
  );
  ok(BrowserTestUtils.isVisible(button), "Permission remove button is visible");
  button.click();
  is(
    Services.perms.testExactPermissionFromPrincipal(principal, "cookie"),
    Services.perms.UNKNOWN_ACTION,
    "Button click should remove cookie pref."
  );
  ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");

  BrowserTestUtils.removeTab(tab);

  Services.prefs.clearUserPref(TPC_PREF);
});

add_task(async function testCookiesSubViewAllowedHeuristic() {
  Services.prefs.setIntPref(
    TPC_PREF,
    Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
  );
  Services.prefs.setBoolPref(
    "privacy.restrict3rdpartystorage.heuristic.exclude_third_party_trackers",
    false
  );
  let principal =
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      "http://not-tracking.example.com/"
    );

  // Pretend that the tracker has already been interacted with
  let trackerPrincipal =
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(
      // eslint-disable-next-line @microsoft/sdl/no-insecure-url
      "http://trackertest.org/"
    );
  Services.perms.addFromPrincipal(
    trackerPrincipal,
    "storageAccessAPI",
    Services.perms.ALLOW_ACTION
  );

  let promise = BrowserTestUtils.openNewForegroundTab({
    url: COOKIE_PAGE,
    gBrowser,
  });
  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(5)]);
  let browser = tab.linkedBrowser;

  let popup;
  let windowCreated = TestUtils.topicObserved(
    "chrome-document-global-created",
    subject => {
      popup = subject;
      return true;
    }
  );
  let permChanged = TestUtils.topicObserved("perm-changed", (subject, data) => {
    return (
      subject &&
      subject.QueryInterface(Ci.nsIPermission).type ==
        "3rdPartyStorage^http://trackertest.org" &&
      subject.principal.origin == principal.origin &&
      data == "added"
    );
  });

  await SpecialPowers.spawn(browser, [], function () {
    content.postMessage("window-open", "*");
  });
  await Promise.all([windowCreated, permChanged]);

  await new Promise(resolve => waitForFocus(resolve, popup));
  await new Promise(resolve => waitForFocus(resolve, window));

  await openProtectionsPanel();

  let categoryItem = document.getElementById(
    "protections-popup-category-cookies"
  );

  // Explicitly waiting for the category item becoming visible.
  await TestUtils.waitForCondition(() => {
    return BrowserTestUtils.isVisible(categoryItem);
  });

  ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
  let cookiesView = document.getElementById("protections-popup-cookiesView");
  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
  categoryItem.click();
  await viewShown;

  ok(true, "Cookies view was shown");

  let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
  is(listItems.length, 1, "We have 1 cookie in the list");

  let listItem = listItems[0];
  let label = listItem.querySelector(".protections-popup-list-host-label");
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  is(label.value, "http://trackertest.org", "has an item for trackertest.org");
  ok(BrowserTestUtils.isVisible(listItem), "list item is visible");
  ok(
    listItem.classList.contains("allowed"),
    "indicates whether the cookie was blocked or allowed"
  );

  let button = listItem.querySelector(
    ".permission-popup-permission-remove-button"
  );
  ok(BrowserTestUtils.isVisible(button), "Permission remove button is visible");
  button.click();
  is(
    Services.perms.testExactPermissionFromPrincipal(
      principal,
      "3rdPartyStorage^http://trackertest.org"
    ),
    Services.perms.UNKNOWN_ACTION,
    "Button click should remove the storage pref."
  );
  ok(!listItem.classList.contains("allowed"), "Has removed the allowed class");

  await SpecialPowers.spawn(browser, [], function () {
    content.postMessage("window-close", "*");
  });

  BrowserTestUtils.removeTab(tab);

  Services.prefs.clearUserPref(TPC_PREF);
  Services.prefs.clearUserPref(
    "privacy.restrict3rdpartystorage.heuristic.exclude_third_party_trackers"
  );
});

add_task(async function testCookiesSubViewBlockedDoublyNested() {
  requestLongerTimeout(2);

  Services.prefs.setIntPref(
    TPC_PREF,
    Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER
  );

  let promise = BrowserTestUtils.openNewForegroundTab({
    url: CONTAINER_PAGE,
    gBrowser,
  });
  let [tab] = await Promise.all([promise, waitForContentBlockingEvent(3)]);

  await openProtectionsPanel();

  let categoryItem = document.getElementById(
    "protections-popup-category-cookies"
  );

  // Explicitly waiting for the category item becoming visible.
  await TestUtils.waitForCondition(() => {
    return BrowserTestUtils.isVisible(categoryItem);
  });

  ok(BrowserTestUtils.isVisible(categoryItem), "TP category item is visible");
  let cookiesView = document.getElementById("protections-popup-cookiesView");
  let viewShown = BrowserTestUtils.waitForEvent(cookiesView, "ViewShown");
  categoryItem.click();
  await viewShown;

  ok(true, "Cookies view was shown");

  let listItems = cookiesView.querySelectorAll(".protections-popup-list-item");
  is(listItems.length, 1, "We have 1 cookie in the list");

  let listItem = listItems[0];
  let label = listItem.querySelector(".protections-popup-list-host-label");
  // eslint-disable-next-line @microsoft/sdl/no-insecure-url
  is(label.value, "http://trackertest.org", "has an item for trackertest.org");
  ok(BrowserTestUtils.isVisible(listItem), "list item is visible");
  ok(
    !listItem.classList.contains("allowed"),
    "indicates whether the cookie was blocked or allowed"
  );

  let button = listItem.querySelector(
    ".permission-popup-permission-remove-button"
  );
  ok(!button, "Permission remove button doesn't exist");

  BrowserTestUtils.removeTab(tab);

  Services.prefs.clearUserPref(TPC_PREF);
});
