/**
 * In this test, we check that the content can't open more than one popup at a
 * time (depending on "dom.allow_mulitple_popups" preference value).
 */

const TEST_DOMAIN = "https://example.net";
const TEST_PATH = "/browser/dom/base/test/";
const CHROME_DOMAIN = "chrome://mochitests/content";

requestLongerTimeout(2);

function promisePopups(count) {
  let windows = [];
  return new Promise(resolve => {
    if (count == 0) {
      resolve([]);
      return;
    }

    let windowObserver = function (aSubject, aTopic, aData) {
      if (aTopic != "domwindowopened") {
        return;
      }
      windows.push(aSubject);
      if (--count == 0) {
        Services.ww.unregisterNotification(windowObserver);
        SimpleTest.executeSoon(() => resolve(windows));
      }
    };
    Services.ww.registerNotification(windowObserver);
  });
}

async function withTestPage(popupCount, optionsOrCallback, callback) {
  let options = optionsOrCallback;
  if (!callback) {
    callback = optionsOrCallback;
    options = {};
  }

  await SpecialPowers.pushPrefEnv({
    set: [
      // Enable popup blocker
      ["dom.disable_open_during_load", true],
    ],
  });

  let domain = options.chrome ? CHROME_DOMAIN : TEST_DOMAIN;
  let tab = BrowserTestUtils.addTab(
    gBrowser,
    domain + TEST_PATH + "browser_multiple_popups.html" + (options.query || "")
  );
  gBrowser.selectedTab = tab;

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

  let obs = promisePopups(popupCount);

  await callback(browser);

  let windows = await obs;
  ok(true, `We had ${popupCount} windows.`);
  for (let win of windows) {
    if (win.document.readyState !== "complete") {
      await BrowserTestUtils.waitForEvent(win, "load");
    }
    await BrowserTestUtils.closeWindow(win);
  }
  BrowserTestUtils.removeTab(tab);
}

function promisePopupsBlocked(browser, expectedCount) {
  return SpecialPowers.spawn(browser, [expectedCount], count => {
    return new content.Promise(resolve => {
      content.addEventListener("DOMPopupBlocked", function cb() {
        if (--count == 0) {
          content.removeEventListener("DOMPopupBlocked", cb);
          ok(true, "The popup has been blocked");
          resolve();
        }
      });
    });
  });
}

function startOpeningTwoPopups(browser) {
  return SpecialPowers.spawn(browser.browsingContext, [], () => {
    let p = content.document.createElement("p");
    p.setAttribute("id", "start");
    content.document.body.appendChild(p);
  });
}

add_task(async _ => {
  info("2 window.open()s in a click event allowed because whitelisted domain.");

  const uri = Services.io.newURI(TEST_DOMAIN);
  const principal = Services.scriptSecurityManager.createContentPrincipal(
    uri,
    {}
  );

  Services.perms.addFromPrincipal(
    principal,
    "popup",
    Services.perms.ALLOW_ACTION
  );

  await withTestPage(2, async function (browser) {
    await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
  });

  await new Promise(resolve => {
    Services.clearData.deleteData(
      Ci.nsIClearDataService.CLEAR_PERMISSIONS,
      value => {
        Assert.equal(value, 0);
        resolve();
      }
    );
  });
});

add_task(async _ => {
  info(
    "2 window.open()s in a mouseup event allowed because whitelisted domain."
  );

  const uri = Services.io.newURI(TEST_DOMAIN);
  const principal = Services.scriptSecurityManager.createContentPrincipal(
    uri,
    {}
  );

  Services.perms.addFromPrincipal(
    principal,
    "popup",
    Services.perms.ALLOW_ACTION
  );

  await withTestPage(2, async function (browser) {
    await BrowserTestUtils.synthesizeMouseAtCenter("#input", {}, browser);
  });

  await new Promise(aResolve => {
    Services.clearData.deleteData(
      Ci.nsIClearDataService.CLEAR_PERMISSIONS,
      value => {
        Assert.equal(value, 0);
        aResolve();
      }
    );
  });
});

add_task(async _ => {
  info(
    "2 window.open()s in a single click event: only the first one is allowed."
  );

  await withTestPage(1, async function (browser) {
    let p = promisePopupsBlocked(browser, 1);
    await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
    await p;
  });
});

add_task(async _ => {
  info(
    "2 window.open()s in a single mouseup event: only the first one is allowed."
  );

  await withTestPage(1, async function (browser) {
    let p = promisePopupsBlocked(browser, 1);
    await BrowserTestUtils.synthesizeMouseAtCenter("#input", {}, browser);
    await p;
  });
});

add_task(async _ => {
  info("2 window.open()s by non-event code: no windows allowed.");

  await withTestPage(0, { query: "?openPopups" }, async function (browser) {
    let p = promisePopupsBlocked(browser, 2);
    await startOpeningTwoPopups(browser);
    await p;
  });
});

add_task(async _ => {
  info("2 window.open()s by non-event code allowed by permission");
  const uri = Services.io.newURI(TEST_DOMAIN);
  const principal = Services.scriptSecurityManager.createContentPrincipal(
    uri,
    {}
  );

  Services.perms.addFromPrincipal(
    principal,
    "popup",
    Services.perms.ALLOW_ACTION
  );

  await withTestPage(2, { query: "?openPopups" }, async function (browser) {
    await startOpeningTwoPopups(browser);
  });

  await new Promise(aResolve => {
    Services.clearData.deleteData(
      Ci.nsIClearDataService.CLEAR_PERMISSIONS,
      value => {
        Assert.equal(value, 0);
        aResolve();
      }
    );
  });
});

add_task(async _ => {
  info(
    "1 window.open() executing another window.open(): only the first one is allowed."
  );

  await withTestPage(1, async function (browser) {
    await BrowserTestUtils.synthesizeMouseAtCenter(
      "#openNestedPopups",
      {},
      browser
    );
  });
});

add_task(async _ => {
  info("window.open() and .click() on the element opening the window.");

  await withTestPage(1, async function (browser) {
    let p = promisePopupsBlocked(browser, 1);

    await BrowserTestUtils.synthesizeMouseAtCenter(
      "#openPopupAndClick",
      {},
      browser
    );

    await p;
  });
});

add_task(async _ => {
  info("All opened from chrome.");
  await withTestPage(2, { chrome: true }, async function (browser) {
    await BrowserTestUtils.synthesizeMouseAtCenter("#openPopups", {}, browser);
  });
});

add_task(async function test_bug_1685056() {
  info(
    "window.open() from a blank iframe window during an event dispatched at the parent page: window should be allowed"
  );

  await withTestPage(1, async function (browser) {
    await BrowserTestUtils.synthesizeMouseAtCenter(
      "#openPopupInFrame",
      {},
      browser
    );
  });
});

add_task(async function test_bug_1689853() {
  info("window.open() from a js bookmark (LOAD_FLAGS_ALLOW_POPUPS)");
  await withTestPage(1, async function (browser) {
    const URI =
      "javascript:void(window.open('empty.html', '_blank', 'width=100,height=100'));";
    window.openTrustedLinkIn(URI, "current", {
      allowPopups: true,
      inBackground: false,
      allowInheritPrincipal: true,
    });
  });
});
