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

/**
 * Test that we get the expected frame enter/exit logs in the tracer view.
 */

const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";

let gTab, gPanel, gDebugger;

function test() {
  SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
    initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
      gTab = aTab;
      gPanel = aPanel;
      gDebugger = gPanel.panelWin;

      waitForSourceShown(gPanel, "code_tracing-01.js")
        .then(() => startTracing(gPanel))
        .then(clickButton)
        .then(() => waitForClientEvents(aPanel, "traces"))
        .then(testTraceLogs)
        .then(() => stopTracing(gPanel))
        .then(() => {
          const deferred = promise.defer();
          SpecialPowers.popPrefEnv(deferred.resolve);
          return deferred.promise;
        })
        .then(() => closeDebuggerAndFinish(gPanel))
        .then(null, aError => {
          ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
        });
    });
  });
}

function clickButton() {
  sendMouseClickToTab(gTab, content.document.querySelector("button"));
}

function testTraceLogs() {
  const onclickLogs = filterTraces(gPanel,
                                   t => t.querySelector(".trace-name[value=onclick]"));
  is(onclickLogs.length, 2, "Should have two logs from 'onclick'");
  ok(onclickLogs[0].querySelector(".trace-call"),
     "The first 'onclick' log should be a call.");
  ok(onclickLogs[1].querySelector(".trace-return"),
     "The second 'onclick' log should be a return.");
  for (let t of onclickLogs) {
    ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
        .contains("doc_tracing-01.html"));
  }

  const nonOnclickLogs = filterTraces(gPanel,
                                      t => !t.querySelector(".trace-name[value=onclick]"));
  for (let t of nonOnclickLogs) {
    ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
        .contains("code_tracing-01.js"));
  }

  const mainLogs = filterTraces(gPanel,
                                t => t.querySelector(".trace-name[value=main]"));
  is(mainLogs.length, 2, "Should have an enter and an exit for 'main'");
  ok(mainLogs[0].querySelector(".trace-call"),
     "The first 'main' log should be a call.");
  ok(mainLogs[1].querySelector(".trace-return"),
     "The second 'main' log should be a return.");

  const factorialLogs = filterTraces(gPanel,
                                     t => t.querySelector(".trace-name[value=factorial]"));
  is(factorialLogs.length, 10, "Should have 5 enter, and 5 exit frames for 'factorial'");
  ok(factorialLogs.slice(0, 5).every(t => t.querySelector(".trace-call")),
     "The first five 'factorial' logs should be calls.");
  ok(factorialLogs.slice(5).every(t => t.querySelector(".trace-return")),
     "The second five 'factorial' logs should be returns.")

  // Test that the depth affects padding so that calls are indented properly.
  let lastDepth = -Infinity;
  for (let t of factorialLogs.slice(0, 5)) {
    let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
    ok(depth > lastDepth, "The depth should be increasing");
    lastDepth = depth;
  }
  lastDepth = Infinity;
  for (let t of factorialLogs.slice(5)) {
    let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
    ok(depth < lastDepth, "The depth should be decreasing");
    lastDepth = depth;
  }

  const throwerLogs = filterTraces(gPanel,
                                   t => t.querySelector(".trace-name[value=thrower]"));
  is(throwerLogs.length, 2, "Should have an enter and an exit for 'thrower'");
  ok(throwerLogs[0].querySelector(".trace-call"),
     "The first 'thrower' log should be a call.");
  ok(throwerLogs[1].querySelector(".trace-throw",
     "The second 'thrower' log should be a throw."));
}

registerCleanupFunction(function() {
  gTab = null;
  gPanel = null;
  gDebugger = null;
});
