/* global allowListed */

async function hasStorageAccessInitially() {
  let hasAccess = await document.hasStorageAccess();
  ok(hasAccess, "Has storage access");
}

async function noStorageAccessInitially() {
  let hasAccess = await document.hasStorageAccess();
  ok(!hasAccess, "Doesn't yet have storage access");
}

async function callRequestStorageAccess(callback, expectFail) {
  let dwu = SpecialPowers.getDOMWindowUtils(window);
  let helper = dwu.setHandlingUserInput(true);

  let origin = new URL(location.href).origin;

  let success = true;
  // We only grant storage exceptions when the reject tracker behavior is enabled.
  let rejectTrackers = SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") ==
                         SpecialPowers.Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER &&
                       !isOnContentBlockingAllowList();
  // With another-tracking.example.net, we're same-eTLD+1, so the first try succeeds.
  if (origin != "https://another-tracking.example.net") {
    if (rejectTrackers) {
      let p;
      let threw = false;
      try {
        p = document.requestStorageAccess();
      } catch (e) {
        threw = true;
      } finally {
        helper.destruct();
      }
      ok(!threw, "requestStorageAccess should not throw");
      try {
        if (callback) {
          if (expectFail) {
            await p.catch(_ => callback(dwu));
            success = false;
          } else {
            await p.then(_ => callback(dwu));
          }
        } else {
          await p;
        }
      } catch (e) {
        success = false;
      }
      ok(!success, "Should not have worked without user interaction");

      await noStorageAccessInitially();

      await interactWithTracker();

      helper = dwu.setHandlingUserInput(true);
    }
    if (SpecialPowers.Services.prefs.getIntPref("network.cookie.cookieBehavior") ==
          SpecialPowers.Ci.nsICookieService.BEHAVIOR_ACCEPT &&
        !isOnContentBlockingAllowList()) {
      try {
        if (callback) {
          if (expectFail) {
            await document.requestStorageAccess().catch(_ => callback(dwu));
            success = false;
          } else {
            await document.requestStorageAccess().then(_ => callback(dwu));
          }
        } else {
          await document.requestStorageAccess();
        }
      } catch (e) {
        success = false;
      } finally {
        helper.destruct();
      }
      ok(success, "Should not have thrown");

      await hasStorageAccessInitially();

      await interactWithTracker();

      helper = dwu.setHandlingUserInput(true);
    }
  }

  let p;
  let threw = false;
  try {
    p = document.requestStorageAccess();
  } catch (e) {
    threw = true;
  } finally {
    helper.destruct();
  }
  let rejected = false;
  try {
    if (callback) {
      if (expectFail) {
        await p.catch(_ => callback(dwu));
        rejected = true;
      } else {
        await p.then(_ => callback(dwu));
      }
    } else {
      await p;
    }
  } catch (e) {
    rejected = true;
  }

  success = !threw && !rejected;
  let hasAccess = await document.hasStorageAccess();
  is(hasAccess, success,
     "Should " + (success ? "" : "not ") + "have storage access now");
  if (success && rejectTrackers &&
      window.location.search != "?disableWaitUntilPermission" &&
      origin != "https://another-tracking.example.net") {
    // Wait until the permission is visible in our process to avoid race
    // conditions.
    await waitUntilPermission("http://example.net/browser/toolkit/components/antitracking/test/browser/page.html",
                              "3rdPartyStorage^https://tracking.example.org");
  }

  return [threw, rejected];
}

async function waitUntilPermission(url, name) {
  await new Promise(resolve => {
    let id = setInterval(_ => {
      let Services = SpecialPowers.Services;
      let uri = Services.io.newURI(url);
      if (Services.perms.testPermission(uri, name) ==
            Services.perms.ALLOW_ACTION) {
        clearInterval(id);
        resolve();
      }
    }, 0);
  });
}

async function interactWithTracker() {
  await new Promise(resolve => {
    onmessage = resolve;

    info("Let's interact with the tracker");
    window.open("https://tracking.example.org/browser/toolkit/components/antitracking/test/browser/3rdPartyOpenUI.html?messageme");
  });

  // Wait until the user interaction permission becomes visible in our process
  await waitUntilPermission("https://tracking.example.org",
                            "storageAccessAPI");
}

function isOnContentBlockingAllowList() {
  let prefs = ["browser.contentblocking.allowlist.storage.enabled",
               "browser.contentblocking.allowlist.annotations.enabled"];
  function allEnabled(prev, pref) {
    return pref &&
           SpecialPowers.Services.prefs.getBoolPref(pref);
  }
  if (!prefs.reduce(allEnabled)) {
    return false;
  }

  let url = new URL(SpecialPowers.wrap(top).location.href);
  let origin = SpecialPowers.Services.io.newURI("https://" + url.host);
  let types = ["trackingprotection", "trackingprotection-pb"];
  return types.some(type => {
    return SpecialPowers.Services.perms.testPermission(origin, type) ==
             SpecialPowers.Services.perms.ALLOW_ACTION;
  });
}

