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

"use strict";

const TEST_FILE = "test-network-request.html";
const TEST_PATH =
  "https://example.com/browser/devtools/client/webconsole/test/browser/";
const TEST_URI = TEST_PATH + TEST_FILE;
const XHR_URL = TEST_PATH + "sjs_slow-response-test-server.sjs";

requestLongerTimeout(2);

registerCleanupFunction(async function() {
  await new Promise(resolve => {
    Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value =>
      resolve()
    );
  });
});

pushPref("devtools.webconsole.filter.net", false);
pushPref("devtools.webconsole.filter.netxhr", true);

/**
 * Main test for checking HTTP logs in the Console panel.
 */
add_task(async function task() {
  const hud = await openNewTabAndConsole(TEST_URI);

  const messageNode = await doXhrAndExpand(hud);

  await testNetworkMessage(hud.toolbox, messageNode);

  await closeToolbox();
});

add_task(async function task() {
  info(
    "Verify that devtools.netmonitor.saveRequestAndResponseBodies=false disable response content collection"
  );
  await pushPref("devtools.netmonitor.saveRequestAndResponseBodies", false);
  const hud = await openNewTabAndConsole(TEST_URI);

  const messageNode = await doXhrAndExpand(hud);

  const responseTab = messageNode.querySelector("#response-tab");
  ok(responseTab, "Response tab is available");

  const { TEST_EVENTS } = require("devtools/client/netmonitor/src/constants");
  const onResponseContent = hud.ui.once(TEST_EVENTS.RECEIVED_RESPONSE_CONTENT);
  // Select Response tab and check the content.
  responseTab.click();

  // Even if response content aren't collected by NetworkObserver,
  // we do try to fetch the content via an RDP request, which
  // we try to wait for here.
  info("Wait for the async getResponseContent request");
  await onResponseContent;
  const responsePanel = messageNode.querySelector("#response-panel");

  // This is updated only after we tried to fetch the response content
  // and fired the getResponseContent request
  info("Wait for the empty response content");
  ok(
    responsePanel.querySelector("div.empty-notice"),
    "An empty notice is displayed instead of the response content"
  );
  const responseContent = messageNode.querySelector(
    "#response-panel .editor-row-container .CodeMirror"
  );
  ok(!responseContent, "Response content is really not displayed");

  await waitForLazyRequests(hud.toolbox);
  await closeToolbox();
});

async function doXhrAndExpand(hud) {
  // Execute XHR and expand it after all network
  // update events are received. Consequently,
  // check out content of all (HTTP details) tabs.
  const onMessage = waitForMessageByType(hud, XHR_URL, ".network");
  const onRequestUpdates = waitForRequestUpdates(hud);
  const onPayloadReady = waitForPayloadReady(hud);

  // Fire an XHR POST request.
  SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
    content.wrappedJSObject.testXhrPostSlowResponse();
  });

  const { node: messageNode } = await onMessage;
  ok(messageNode, "Network message found.");

  await onRequestUpdates;

  // Expand network log
  await expandXhrMessage(messageNode);

  const toggleButtonNode = messageNode.querySelector(".sidebar-toggle");
  ok(!toggleButtonNode, "Sidebar toggle button shouldn't be shown");

  await onPayloadReady;

  return messageNode;
}

// Panel testing helpers

async function testNetworkMessage(toolbox, messageNode) {
  await testStatusInfo(messageNode);
  await testHeaders(messageNode);
  await testCookies(messageNode);
  await testRequest(messageNode);
  await testResponse(messageNode);
  await testTimings(messageNode);
  await testStackTrace(messageNode);
  await testSecurity(messageNode);
  await waitForLazyRequests(toolbox);
}

// Status Info

function testStatusInfo(messageNode) {
  const statusInfo = messageNode.querySelector(".status-info");
  ok(statusInfo, "Status info is not empty");
}

// Headers
async function testHeaders(messageNode) {
  const headersTab = messageNode.querySelector("#headers-tab");
  ok(headersTab, "Headers tab is available");

  // Select Headers tab and check the content.
  headersTab.click();
  await waitFor(
    () => messageNode.querySelector("#headers-panel .headers-overview"),
    "Wait for .header-overview to be rendered"
  );
}

// Cookies
async function testCookies(messageNode) {
  const cookiesTab = messageNode.querySelector("#cookies-tab");
  ok(cookiesTab, "Cookies tab is available");

  // Select tab and check the content.
  cookiesTab.click();
  await waitFor(
    () => messageNode.querySelector("#cookies-panel .treeValueCell"),
    "Wait for .treeValueCell to be rendered"
  );
}

// Request
async function testRequest(messageNode) {
  const requestTab = messageNode.querySelector("#request-tab");
  ok(requestTab, "Request tab is available");

  // Select Request tab and check the content. CodeMirror initialization
  // is delayed to prevent UI freeze, so wait for a little while.
  requestTab.click();
  const requestPanel = messageNode.querySelector("#request-panel");
  await waitForSourceEditor(requestPanel);
  const requestContent = requestPanel.querySelector(
    ".panel-container .CodeMirror"
  );
  ok(requestContent, "Request content is available");
  ok(
    requestContent.textContent.includes("Hello world!"),
    "Request POST body is correct"
  );
}

// Response
async function testResponse(messageNode) {
  const responseTab = messageNode.querySelector("#response-tab");
  ok(responseTab, "Response tab is available");

  // Select Response tab and check the content. CodeMirror initialization
  // is delayed, so again wait for a little while.
  responseTab.click();
  const responsePanel = messageNode.querySelector("#response-panel");
  await waitForSourceEditor(responsePanel);
  const responseContent = messageNode.querySelector(
    "#response-panel .editor-row-container .CodeMirror"
  );
  ok(responseContent, "Response content is available");
  ok(responseContent.textContent, "Response text is available");
}

// Timings
async function testTimings(messageNode) {
  const timingsTab = messageNode.querySelector("#timings-tab");
  ok(timingsTab, "Timings tab is available");

  // Select Timings tab and check the content.
  timingsTab.click();
  const timingsContent = await waitFor(() =>
    messageNode.querySelector(
      "#timings-panel .timings-container .timings-label",
      "Wait for .timings-label to be rendered"
    )
  );
  ok(timingsContent, "Timings content is available");
  ok(timingsContent.textContent, "Timings text is available");
}

// Stack Trace
async function testStackTrace(messageNode) {
  const stackTraceTab = messageNode.querySelector("#stack-trace-tab");
  ok(stackTraceTab, "StackTrace tab is available");

  // Select Stack Trace tab and check the content.
  stackTraceTab.click();
  await waitFor(
    () => messageNode.querySelector("#stack-trace-panel .frame-link"),
    "Wait for .frame-link to be rendered"
  );
}

// Security
async function testSecurity(messageNode) {
  const securityTab = messageNode.querySelector("#security-tab");
  ok(securityTab, "Security tab is available");

  // Select Security tab and check the content.
  securityTab.click();
  await waitFor(
    () => messageNode.querySelector("#security-panel .treeTable .treeRow"),
    "Wait for #security-panel .treeTable .treeRow to be rendered"
  );
}

// Waiting helpers

async function waitForPayloadReady(hud) {
  return hud.ui.once("network-request-payload-ready");
}

async function waitForSourceEditor(panel) {
  return waitUntil(() => {
    return !!panel.querySelector(".CodeMirror");
  });
}

async function waitForRequestUpdates(hud) {
  return hud.ui.once("network-messages-updated");
}

function expandXhrMessage(node) {
  info(
    "Click on XHR message and wait for the network detail panel to be displayed"
  );
  node.querySelector(".url").click();
  return waitFor(
    () => node.querySelector(".network-info"),
    "Wait for .network-info to be rendered"
  );
}
