/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

/**
 * Tests that the break-on-dom-events request works.
 */

const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html";

var gClient, gThreadClient, gInput, gButton;

function test() {
  if (!DebuggerServer.initialized) {
    DebuggerServer.init();
    DebuggerServer.addBrowserActors();
  }

  let transport = DebuggerServer.connectPipe();
  gClient = new DebuggerClient(transport);
  gClient.connect().then(([aType, aTraits]) => {
    is(aType, "browser",
      "Root actor should identify itself as a browser.");

    addTab(TAB_URL)
      .then(() => attachThreadActorForUrl(gClient, TAB_URL))
      .then(setupGlobals)
      .then(pauseDebuggee)
      .then(testBreakOnAll)
      .then(testBreakOnDisabled)
      .then(testBreakOnNone)
      .then(testBreakOnClick)
      .then(() => gClient.close())
      .then(finish)
      .catch(aError => {
        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
      });
  });
}

function setupGlobals(aThreadClient) {
  gThreadClient = aThreadClient;
  gInput = content.document.querySelector("input");
  gButton = content.document.querySelector("button");
}

function pauseDebuggee() {
  let deferred = promise.defer();

  gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
    is(aPacket.type, "paused",
      "We should now be paused.");
    is(aPacket.why.type, "debuggerStatement",
      "The debugger statement was hit.");

    deferred.resolve();
  });

  // Spin the event loop before causing the debuggee to pause, to allow
  // this function to return first.
  executeSoon(triggerButtonClick);

  return deferred.promise;
}

// Test pause on all events.
function testBreakOnAll() {
  let deferred = promise.defer();

  // Test calling pauseOnDOMEvents from a paused state.
  gThreadClient.pauseOnDOMEvents("*", (aPacket) => {
    is(aPacket.error, undefined,
      "The pause-on-any-event request completed successfully.");

    gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
      is(aPacket.why.type, "pauseOnDOMEvents",
        "A hidden breakpoint was hit.");
      is(aPacket.frame.callee.name, "keyupHandler",
        "The keyupHandler is entered.");

      gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
        is(aPacket.why.type, "pauseOnDOMEvents",
          "A hidden breakpoint was hit.");
        is(aPacket.frame.callee.name, "clickHandler",
          "The clickHandler is entered.");

        gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
          is(aPacket.why.type, "pauseOnDOMEvents",
            "A hidden breakpoint was hit.");
          is(aPacket.frame.callee.name, "onchange",
            "The onchange handler is entered.");

          gThreadClient.resume(deferred.resolve);
        });

        gThreadClient.resume(triggerInputChange);
      });

      gThreadClient.resume(triggerButtonClick);
    });

    gThreadClient.resume(triggerInputKeyup);
  });

  return deferred.promise;
}

// Test that removing events from the array disables them.
function testBreakOnDisabled() {
  let deferred = promise.defer();

  // Test calling pauseOnDOMEvents from a running state.
  gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
    is(aPacket.error, undefined,
      "The pause-on-click-only request completed successfully.");

    gClient.addListener("paused", unexpectedListener);

    // This non-capturing event listener is guaranteed to run after the page's
    // capturing one had a chance to execute and modify window.foobar.
    once(gInput, "keyup").then(() => {
      is(content.wrappedJSObject.foobar, "keyupHandler",
        "No hidden breakpoint was hit.");

      gClient.removeListener("paused", unexpectedListener);
      deferred.resolve();
    });

    triggerInputKeyup();
  });

  return deferred.promise;
}

// Test that specifying an empty event array clears all hidden breakpoints.
function testBreakOnNone() {
  let deferred = promise.defer();

  // Test calling pauseOnDOMEvents from a running state.
  gThreadClient.pauseOnDOMEvents([], (aPacket) => {
    is(aPacket.error, undefined,
      "The pause-on-none request completed successfully.");

    gClient.addListener("paused", unexpectedListener);

    // This non-capturing event listener is guaranteed to run after the page's
    // capturing one had a chance to execute and modify window.foobar.
    once(gInput, "keyup").then(() => {
      is(content.wrappedJSObject.foobar, "keyupHandler",
        "No hidden breakpoint was hit.");

      gClient.removeListener("paused", unexpectedListener);
      deferred.resolve();
    });

    triggerInputKeyup();
  });

  return deferred.promise;
}

// Test pause on a single event.
function testBreakOnClick() {
  let deferred = promise.defer();

  // Test calling pauseOnDOMEvents from a running state.
  gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => {
    is(aPacket.error, undefined,
      "The pause-on-click request completed successfully.");

    gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
      is(aPacket.why.type, "pauseOnDOMEvents",
        "A hidden breakpoint was hit.");
      is(aPacket.frame.callee.name, "clickHandler",
        "The clickHandler is entered.");

      gThreadClient.resume(deferred.resolve);
    });

    triggerButtonClick();
  });

  return deferred.promise;
}

function unexpectedListener() {
  gClient.removeListener("paused", unexpectedListener);
  ok(false, "An unexpected hidden breakpoint was hit.");
  gThreadClient.resume(testBreakOnClick);
}

function triggerInputKeyup() {
  // Make sure that the focus is not on the input box so that a focus event
  // will be triggered.
  window.focus();
  gBrowser.selectedBrowser.focus();
  gButton.focus();

  // Focus the element and wait for focus event.
  once(gInput, "focus").then(() => {
    executeSoon(() => {
      EventUtils.synthesizeKey("e", { shiftKey: 1 }, content);
    });
  });

  gInput.focus();
}

function triggerButtonClick() {
  EventUtils.sendMouseEvent({ type: "click" }, gButton);
}

function triggerInputChange() {
  gInput.focus();
  gInput.value = "foo";
  gInput.blur();
}

registerCleanupFunction(function () {
  gClient = null;
  gThreadClient = null;
  gInput = null;
  gButton = null;
});
