/* eslint-disable mozilla/no-arbitrary-setTimeout */
Services.scriptloader.loadSubScript(
  "chrome://mochitests/content/browser/browser/modules/test/browser/head.js",
  this
);

const BLOCK = 0;
const ALLOW = 1;

async function testDoorHanger(
  choice,
  showPrompt,
  useEscape,
  topPage,
  maxConcurrent,
  disableWebcompat = false
) {
  info(
    `Running doorhanger test with choice #${choice}, showPrompt: ${showPrompt} and ` +
      `useEscape: ${useEscape}, topPage: ${topPage}, maxConcurrent: ${maxConcurrent}`
  );

  if (!showPrompt) {
    is(choice, ALLOW, "When not showing a prompt, we can only auto-grant");
    ok(
      !useEscape,
      "When not showing a prompt, we should not be trying to use the Esc key"
    );
  }

  await SpecialPowers.flushPrefEnv();
  await SpecialPowers.pushPrefEnv({
    set: [
      ["privacy.antitracking.enableWebcompat", !disableWebcompat],
      ["dom.storage_access.auto_grants", true],
      ["dom.storage_access.auto_grants.delayed", false],
      ["dom.storage_access.enabled", true],
      ["dom.storage_access.max_concurrent_auto_grants", maxConcurrent],
      ["dom.storage_access.prompt.testing", false],
      [
        "network.cookie.cookieBehavior",
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
      ],
      [
        "network.cookie.cookieBehavior.pbmode",
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER,
      ],
      ["privacy.trackingprotection.enabled", false],
      ["privacy.trackingprotection.pbmode.enabled", false],
      ["privacy.trackingprotection.annotate_channels", true],
      [
        "privacy.restrict3rdpartystorage.userInteractionRequiredForHosts",
        "tracking.example.com,tracking.example.org",
      ],
      // Bug 1617611: Fix all the tests broken by "cookies SameSite=lax by default"
      ["network.cookie.sameSite.laxByDefault", false],
      ["dom.storage_access.auto_grants.exclude_third_party_trackers", false],
    ],
  });

  await UrlClassifierTestUtils.addTestTrackers();

  let tab = BrowserTestUtils.addTab(gBrowser, topPage);
  gBrowser.selectedTab = tab;

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

  async function runChecks() {
    // We need to repeat this constant here since runChecks is stringified
    // and sent to the content process.
    const BLOCK = 0;

    await new Promise(resolve => {
      addEventListener(
        "message",
        function onMessage(e) {
          if (e.data.startsWith("choice:")) {
            window.choice = e.data.split(":")[1];
            window.useEscape = e.data.split(":")[3];
            removeEventListener("message", onMessage);
            resolve();
          }
        },
        false
      );
      parent.postMessage("getchoice", "*");
    });

    /* import-globals-from storageAccessAPIHelpers.js */
    await noStorageAccessInitially();

    is(document.cookie, "", "No cookies for me");
    document.cookie = "name=value";
    is(document.cookie, "", "No cookies for me");

    await fetch("server.sjs")
      .then(r => r.text())
      .then(text => {
        is(text, "cookie-not-present", "We should not have cookies");
      });
    // Let's do it twice.
    await fetch("server.sjs")
      .then(r => r.text())
      .then(text => {
        is(text, "cookie-not-present", "We should not have cookies");
      });

    is(document.cookie, "", "Still no cookies for me");

    /* import-globals-from storageAccessAPIHelpers.js */
    await callRequestStorageAccess();

    if (choice == BLOCK) {
      // We've said no, so cookies are still blocked
      is(document.cookie, "", "Still no cookies for me");
      document.cookie = "name=value";
      is(document.cookie, "", "No cookies for me");
    } else {
      // We've said yes, so cookies are allowed now
      is(document.cookie, "", "No cookies for me");
      document.cookie = "name=value";
      is(document.cookie, "name=value", "I have the cookies!");
    }
  }

  let permChanged;
  // Only create the promise when we're going to click one of the allow buttons.
  if (choice != BLOCK) {
    permChanged = TestUtils.topicObserved("perm-changed", (subject, data) => {
      let result;
      if (choice == ALLOW) {
        result =
          subject &&
          subject
            .QueryInterface(Ci.nsIPermission)
            .type.startsWith("3rdPartyFrameStorage^") &&
          subject.principal.origin == new URL(topPage).origin &&
          data == "added";
      }
      return result;
    });
  }
  let shownPromise = BrowserTestUtils.waitForEvent(
    PopupNotifications.panel,
    "popupshown"
  );
  shownPromise.then(async _ => {
    if (topPage != gBrowser.currentURI.spec) {
      return;
    }
    ok(showPrompt, "We shouldn't show the prompt when we don't intend to");
    let notification = await new Promise(function poll(resolve) {
      let notification = PopupNotifications.getNotification(
        "storage-access",
        browser
      );
      if (notification) {
        resolve(notification);
        return;
      }
      setTimeout(poll, 10);
    });
    Assert.ok(notification, "Should have gotten the notification");

    if (choice == BLOCK) {
      if (useEscape) {
        info("hitting escape");
        EventUtils.synthesizeKey("KEY_Escape", {}, window);
      } else {
        await clickSecondaryAction();
      }
    } else if (choice == ALLOW) {
      await clickMainAction();
    }
    if (choice != BLOCK) {
      await permChanged;
    }
  });

  let url = TEST_3RD_PARTY_PAGE + "?disableWaitUntilPermission";
  let ct = SpecialPowers.spawn(
    browser,
    [{ page: url, callback: runChecks.toString(), choice, useEscape }],
    async function (obj) {
      await new content.Promise(resolve => {
        let ifr = content.document.createElement("iframe");
        ifr.onload = function () {
          info("Sending code to the 3rd party content");
          ifr.contentWindow.postMessage(obj.callback, "*");
        };

        content.addEventListener("message", function msg(event) {
          if (event.data.type == "finish") {
            content.removeEventListener("message", msg);
            resolve();
            return;
          }

          if (event.data.type == "ok") {
            ok(event.data.what, event.data.msg);
            return;
          }

          if (event.data.type == "info") {
            info(event.data.msg);
            return;
          }

          if (event.data == "getchoice") {
            ifr.contentWindow.postMessage(
              "choice:" + obj.choice + ":useEscape:" + obj.useEscape,
              "*"
            );
            return;
          }

          ok(false, "Unknown message");
        });

        ifr.src = obj.page;
        content.document.body.appendChild(ifr);
      });
    }
  );
  if (showPrompt) {
    await Promise.all([ct, shownPromise]);
  } else {
    await Promise.all([ct, permChanged]);
  }

  let permissionPopupPromise = BrowserTestUtils.waitForEvent(
    window,
    "popupshown",
    true,
    event => event.target == gPermissionPanel._permissionPopup
  );
  gPermissionPanel._identityPermissionBox.click();
  await permissionPopupPromise;
  let permissionItem = document.querySelector(
    ".permission-popup-permission-item-3rdPartyFrameStorage"
  );
  ok(permissionItem, "Permission item exists");
  ok(
    BrowserTestUtils.isVisible(permissionItem),
    "Permission item visible in the identity panel"
  );
  let permissionLearnMoreLink = document.getElementById(
    "permission-popup-storage-access-permission-learn-more"
  );
  ok(permissionLearnMoreLink, "Permission learn more link exists");
  ok(
    BrowserTestUtils.isVisible(permissionLearnMoreLink),
    "Permission learn more link is visible in the identity panel"
  );
  permissionPopupPromise = BrowserTestUtils.waitForEvent(
    gPermissionPanel._permissionPopup,
    "popuphidden"
  );
  gPermissionPanel._permissionPopup.hidePopup();
  await permissionPopupPromise;

  BrowserTestUtils.removeTab(tab);

  UrlClassifierTestUtils.cleanupTestTrackers();
}

async function preparePermissionsFromOtherSites(topPage) {
  info("Faking permissions from other sites");
  let type = "3rdPartyFrameStorage^https://example.org";
  let permission = Services.perms.ALLOW_ACTION;
  let expireType = Services.perms.EXPIRE_SESSION;
  if (topPage == TEST_TOP_PAGE) {
    // For the first page, don't do anything
  } else if (topPage == TEST_TOP_PAGE_2) {
    // For the second page, only add the permission from the first page
    PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
  } else if (topPage == TEST_TOP_PAGE_3) {
    // For the third page, add the permissions from the first two pages
    PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
  } else if (topPage == TEST_TOP_PAGE_4) {
    // For the fourth page, add the permissions from the first three pages
    PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_3, type, permission, expireType, 0);
  } else if (topPage == TEST_TOP_PAGE_5) {
    // For the fifth page, add the permissions from the first four pages
    PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_3, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_4, type, permission, expireType, 0);
  } else if (topPage == TEST_TOP_PAGE_6) {
    // For the sixth page, add the permissions from the first five pages
    PermissionTestUtils.add(TEST_DOMAIN, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_2, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_3, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_4, type, permission, expireType, 0);
    PermissionTestUtils.add(TEST_DOMAIN_5, type, permission, expireType, 0);
  } else {
    ok(false, "Unexpected top page: " + topPage);
  }
}

async function cleanUp() {
  info("Cleaning up.");
  SpecialPowers.clearUserPref("network.cookie.sameSite.laxByDefault");
  await new Promise(resolve => {
    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, () =>
      resolve()
    );
  });
}

async function runRound(topPage, showPrompt, maxConcurrent, disableWebcompat) {
  info("Starting round");
  if (showPrompt) {
    await preparePermissionsFromOtherSites(topPage);
    await testDoorHanger(
      BLOCK,
      showPrompt,
      true,
      topPage,
      maxConcurrent,
      disableWebcompat
    );
    await cleanUp();
    await preparePermissionsFromOtherSites(topPage);
    await testDoorHanger(
      BLOCK,
      showPrompt,
      false,
      topPage,
      maxConcurrent,
      disableWebcompat
    );
    await cleanUp();
    await preparePermissionsFromOtherSites(topPage);
    await testDoorHanger(
      ALLOW,
      showPrompt,
      false,
      topPage,
      maxConcurrent,
      disableWebcompat
    );
    await cleanUp();
  } else {
    await preparePermissionsFromOtherSites(topPage);
    await testDoorHanger(
      ALLOW,
      showPrompt,
      false,
      topPage,
      maxConcurrent,
      disableWebcompat
    );
  }
  await cleanUp();
}

add_task(async function test_combinations() {
  await runRound(TEST_TOP_PAGE, false, 1);
  await runRound(TEST_TOP_PAGE_2, true, 1);
  await runRound(TEST_TOP_PAGE, false, 5);
  await runRound(TEST_TOP_PAGE_2, false, 5);
  await runRound(TEST_TOP_PAGE_3, false, 5);
  await runRound(TEST_TOP_PAGE_4, false, 5);
  await runRound(TEST_TOP_PAGE_5, false, 5);
  await runRound(TEST_TOP_PAGE_6, true, 5);
});

add_task(async function test_disableWebcompat() {
  await runRound(TEST_TOP_PAGE, true, 5, true);
});
