const BEHAVIOR_ACCEPT = 0;
const BEHAVIOR_REJECT_FOREIGN = 1;
const BEHAVIOR_REJECT = 2;
const BEHAVIOR_LIMIT_FOREIGN = 3;

const kPrefName = "network.cookie.cookieBehavior";

// Check if we are in frame, and declare ok and finishTest appropriately
const inFrame = ("" + location).match(/frame/);
if (inFrame) {
  ok = function (a, message) {
    if (!a) {
      parent.postMessage("FAILURE: " + message, "http://mochi.test:8888");
    } else {
      parent.postMessage(message, "http://mochi.test:8888");
    }
  };

  finishTest = function () {
    parent.postMessage("done", "http://mochi.test:8888");
  };
} else {
  finishTest = function () {
    SimpleTest.finish();
  };
}

function setCookieBehavior(behavior) {
  return SpecialPowers.pushPrefEnv({ set: [[kPrefName, behavior]] });
}

function runIFrame(url) {
  return new Promise((resolve, reject) => {
    function onMessage(e) {
      if (e.data == "done") {
        resolve();
        window.removeEventListener("message", onMessage);
        return;
      }

      const isFail = e.data.match(/^FAILURE/);
      ok(!isFail, e.data + " (IFRAME = " + url + ")");
      if (isFail) {
        reject(e);
      }
    }
    window.addEventListener("message", onMessage);

    document.querySelector("iframe").src = url;
  });
}

function runWorker(url) {
  return new Promise((resolve, reject) => {
    var worker = new Worker(url);
    worker.addEventListener("message", function (e) {
      if (e.data == "done") {
        resolve();
        return;
      }

      ok(!e.data.match(/^FAILURE/), e.data + " (WORKER = " + url + ")");
    });

    worker.addEventListener("error", function (e) {
      ok(false, e.data + " (WORKER = " + url + ")");
      reject(e);
    });
  });
}

function chromePower(allowed, blockSessionStorage) {
  // localStorage is affected by storage policy.
  try {
    SpecialPowers.wrap(window).localStorage.getItem("X");
    ok(allowed, "getting localStorage from chrome didn't throw");
  } catch (e) {
    ok(!allowed, "getting localStorage from chrome threw");
  }

  // sessionStorage is not. See bug 1183968.
  try {
    SpecialPowers.wrap(window).sessionStorage.getItem("X");
    ok(!blockSessionStorage, "getting sessionStorage from chrome didn't throw");
  } catch (e) {
    ok(blockSessionStorage, "getting sessionStorage from chrome threw");
  }

  // indexedDB is affected by storage policy.
  try {
    SpecialPowers.wrap(window).indexedDB;
    ok(allowed, "getting indexedDB from chrome didn't throw");
  } catch (e) {
    ok(!allowed, "getting indexedDB from chrome threw");
  }

  // Same with caches, along with the additional https-only requirement.
  try {
    var shouldResolve = allowed && location.protocol == "https:";
    var promise = SpecialPowers.wrap(window).caches.keys();
    ok(true, "getting caches from chrome should never throw");
    return new Promise((resolve, reject) => {
      promise.then(
        function () {
          ok(shouldResolve, "The promise was resolved for chrome");
          resolve();
        },
        function (e) {
          ok(!shouldResolve, "The promise was rejected for chrome: " + e);
          resolve();
        }
      );
    });
  } catch (e) {
    ok(false, "getting caches from chrome threw");
    return undefined;
  }
}

function storageAllowed() {
  try {
    localStorage.getItem("X");
    ok(true, "getting localStorage didn't throw");
  } catch (e) {
    ok(false, "getting localStorage should not throw");
  }

  try {
    sessionStorage.getItem("X");
    ok(true, "getting sessionStorage didn't throw");
  } catch (e) {
    ok(false, "getting sessionStorage should not throw");
  }

  try {
    indexedDB;
    ok(true, "getting indexedDB didn't throw");
  } catch (e) {
    ok(false, "getting indexedDB should not throw");
  }

  const dbName = "db";

  try {
    var promise = caches.keys();
    ok(true, "getting caches didn't throw");

    return new Promise((resolve, reject) => {
      const checkCacheKeys = () => {
        promise.then(
          () => {
            ok(location.protocol == "https:", "The promise was not rejected");
            resolve();
          },
          () => {
            ok(
              location.protocol !== "https:",
              "The promise should not have been rejected"
            );
            resolve();
          }
        );
      };

      const checkDeleteDbAndTheRest = dbs => {
        ok(
          dbs.some(elem => elem.name === dbName),
          "Expected database should be found"
        );

        const end = indexedDB.deleteDatabase(dbName);
        end.onsuccess = checkCacheKeys;
        end.onerror = err => {
          ok(false, "querying indexedDB databases should not throw");
          reject(err);
        };
      };

      const checkDatabasesAndTheRest = () => {
        indexedDB
          .databases()
          .then(checkDeleteDbAndTheRest)
          .catch(err => {
            ok(false, "deleting an indexedDB database should not throw");
            reject(err);
          });
      };

      const begin = indexedDB.open(dbName);
      begin.onsuccess = checkDatabasesAndTheRest;
      begin.onerror = err => {
        ok(false, "opening an indexedDB database should not throw");
        reject(err);
      };
    });
  } catch (e) {
    ok(location.protocol !== "https:", "getting caches should not have thrown");
    return Promise.resolve();
  }
}

function storagePrevented() {
  try {
    localStorage.getItem("X");
    ok(false, "getting localStorage should have thrown");
  } catch (e) {
    ok(true, "getting localStorage threw");
  }

  if (location.hash == "#thirdparty") {
    // No matter what the user's preferences are, we don't block
    // sessionStorage in 3rd-party iframes. We do block them everywhere
    // else however.
    try {
      sessionStorage.getItem("X");
      ok(true, "getting sessionStorage didn't throw");
    } catch (e) {
      ok(false, "getting sessionStorage should not have thrown");
    }
  } else {
    try {
      sessionStorage.getItem("X");
      ok(false, "getting sessionStorage should have thrown");
    } catch (e) {
      ok(true, "getting sessionStorage threw");
    }
  }

  try {
    indexedDB;
    ok(true, "getting indexedDB didn't throw");
  } catch (e) {
    ok(false, "getting indexedDB should not have thrown");
  }

  const dbName = "album";

  try {
    indexedDB.open(dbName);
    ok(false, "opening an indexedDB database didn't throw");
  } catch (e) {
    ok(true, "opening an indexedDB database threw");
    ok(
      e.name == "SecurityError",
      "opening indexedDB database emitted a security error"
    );
  }

  // Note: Security error is expected to be thrown synchronously.
  indexedDB.databases().then(
    () => {
      ok(false, "querying indexedDB databases didn't reject");
    },
    e => {
      ok(true, "querying indexedDB databases rejected");
      ok(
        e.name == "SecurityError",
        "querying indexedDB databases emitted a security error"
      );
    }
  );

  try {
    indexedDB.deleteDatabase(dbName);
    ok(false, "deleting an indexedDB database didn't throw");
  } catch (e) {
    ok(true, "deleting an indexedDB database threw");
    ok(
      e.name == "SecurityError",
      "deleting indexedDB database emitted a security error"
    );
  }

  try {
    var promise = caches.keys();
    ok(true, "getting caches didn't throw");

    return new Promise((resolve, reject) => {
      promise.then(
        function () {
          ok(false, "The promise should have rejected");
          resolve();
        },
        function () {
          ok(true, "The promise was rejected");
          resolve();
        }
      );
    });
  } catch (e) {
    ok(location.protocol !== "https:", "getting caches should not have thrown");

    return Promise.resolve();
  }
}

function task(fn) {
  if (!inFrame) {
    SimpleTest.waitForExplicitFinish();
  }

  var gen = fn();

  function next_step(val, e) {
    var it;
    try {
      if (typeof e !== "undefined") {
        it = gen.throw(e);
      } else {
        it = gen.next(val);
      }
    } catch (e) {
      ok(false, "An error was thrown while stepping: " + e);
      ok(false, "Stack: " + e.stack);
      finishTest();
    }

    if (it.done) {
      finishTest();
      return;
    }
    it.value.then(next_step, e => next_step(null, e));
  }

  if (!gen.then) {
    next_step();
  } else {
    gen.then(finishTest, e => {
      ok(false, "An error was thrown while stepping: " + e);
      ok(false, "Stack: " + e.stack);
      finishTest();
    });
  }
}

// The test will run on a separate window in order to apply the new cookie jar settings.
async function runTestInWindow(test) {
  let w = window.open("window_storagePermissions.html");
  await new Promise(resolve => {
    w.onload = e => {
      resolve();
    };
  });

  return new Promise((resolve, reject) => {
    onmessage = e => {
      if (!e.data.type) {
        w.postMessage("FAILURE: " + e.data, document.referrer);
        ok(false, "No error data type");
        reject(e);
        return;
      }

      if (e.data.type == "finish") {
        w.close();
        resolve();
        return;
      }

      if (e.data.type == "check") {
        const payload = e.data.msg ? e.data.msg : e.data;
        ok(e.data.test, payload);
        const isFail = payload.match(/^FAILURE/) || !e.data.test;
        if (isFail) {
          w.postMessage("FAILURE: " + e.data, document.referrer);
          ok(false, payload);
          w.close();
          reject(e);
        }
        return;
      }

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

    w.postMessage(test.toString(), "*");
  });
}

var thirdparty = "https://example.com/tests/dom/tests/mochitest/general/";
