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

"use strict";

/**
 * Creates a TCP server which binds to a random available port. When Firefox
 * connects to the server, the OS completes the handshake and onSocketAccepted
 * fires. The callback immediately returns without reading or writing anything,
 * and the server just holds the connection open and says nothing, which forces
 * NS_ERROR_NET_TIMEOUT.
 */
function startHangingServer() {
  const server = Cc["@mozilla.org/network/server-socket;1"].createInstance(
    Ci.nsIServerSocket
  );
  server.init(-1, true, -1);
  const openTransports = [];
  server.asyncListen({
    onSocketAccepted(socket, transport) {
      openTransports.push(transport);
    },
    onStopListening() {},
  });
  registerCleanupFunction(() => {
    for (let transport of openTransports) {
      transport.close(Cr.NS_OK);
    }
    server.close();
  });
  return server.port;
}

async function loadTimeoutErrorPage(url) {
  let browser, tab;
  let pageLoaded;
  await BrowserTestUtils.openNewForegroundTab(
    gBrowser,
    () => {
      gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, url);
      browser = gBrowser.selectedBrowser;
      tab = gBrowser.selectedTab;
      pageLoaded = BrowserTestUtils.waitForErrorPage(browser);
    },
    false
  );
  await pageLoaded;
  return { browser, tab };
}

add_task(async function test_netTimeout_error_page_elements() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["network.http.response.timeout", 1],
      ["network.http.tcp_keepalive.short_lived_connections", false],
      ["network.http.tcp_keepalive.long_lived_connections", false],
    ],
  });

  const port = startHangingServer();
  const url = `http://127.0.0.1:${port}/`;
  const { browser, tab } = await loadTimeoutErrorPage(url);

  await SpecialPowers.spawn(browser, [], async function () {
    await ContentTaskUtils.waitForCondition(
      () => content?.document?.querySelector("net-error-card"),
      "Wait for net-error-card to render"
    );
    const doc = content.document;
    const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject;
    await netErrorCard.getUpdateComplete();

    Assert.equal(
      netErrorCard.errorTitle.dataset.l10nId,
      "netTimeout-title",
      "Using the netTimeout title"
    );
    Assert.equal(
      netErrorCard.errorIntro.dataset.l10nId,
      "fp-neterror-net-timeout-intro",
      "Using the netTimeout intro"
    );
    const whatCanYouDoList = doc
      .querySelector("net-error-card")
      .shadowRoot.querySelector(".what-can-you-do-list");
    Assert.ok(whatCanYouDoList, "The what-can-you-do list is present");
    const items = whatCanYouDoList.querySelectorAll("li");
    Assert.equal(items.length, 3, "Three what-can-you-do items are present");
    Assert.equal(
      items[0].dataset.l10nId,
      "neterror-load-error-try-again",
      "First item has correct l10n ID"
    );
    Assert.equal(
      items[1].dataset.l10nId,
      "neterror-load-error-connection",
      "Second item has correct l10n ID"
    );
    Assert.equal(
      items[2].dataset.l10nId,
      "neterror-load-error-firewall",
      "Third item has correct l10n ID"
    );
    Assert.ok(
      ContentTaskUtils.isVisible(netErrorCard.tryAgainButton),
      "The 'Try Again' button is shown"
    );
  });

  BrowserTestUtils.removeTab(tab);
  await SpecialPowers.popPrefEnv();
});

add_task(async function test_netTimeout_hostname_in_intro() {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["network.http.response.timeout", 1],
      ["network.http.tcp_keepalive.short_lived_connections", false],
      ["network.http.tcp_keepalive.long_lived_connections", false],
    ],
  });

  const port = startHangingServer();
  const url = `http://127.0.0.1:${port}/`;
  const { browser, tab } = await loadTimeoutErrorPage(url);

  await SpecialPowers.spawn(browser, [port], async function (serverPort) {
    await ContentTaskUtils.waitForCondition(
      () => content?.document?.querySelector("net-error-card"),
      "Wait for net-error-card to render"
    );
    const doc = content.document;
    const netErrorCard = doc.querySelector("net-error-card").wrappedJSObject;
    await netErrorCard.getUpdateComplete();

    const l10nArgs = JSON.parse(
      netErrorCard.errorIntro.getAttribute("data-l10n-args")
    );
    Assert.equal(
      l10nArgs.hostname,
      `127.0.0.1:${serverPort}`,
      "Hostname including port is passed to the intro l10n args"
    );
  });

  BrowserTestUtils.removeTab(tab);
  await SpecialPowers.popPrefEnv();
});
