<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=448602
-->
<head>
  <title>Test for Bug 448602</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=448602">Mozilla Bug 448602</a>
<p id="display"></p>
<div id="content" style="display: none">
  
</div>
<pre id="test">          
<script type="application/javascript">

/** Test for Bug 448602 **/

var els, root, l2, l3;

var handlerCalled = false;
var capturingListenerCalled = false;
var bubblingListenerCalled = false;

function clearListenerStates() {
  handlerCalled = false;
  capturingListenerCalled = false;
  bubblingListenerCalled = false;
}

function runTests() {
  els = SpecialPowers.Cc["@mozilla.org/eventlistenerservice;1"]
                     .getService(SpecialPowers.Ci.nsIEventListenerService);

  // Event listener info tests
  root = document.getElementById("testroot");
  var infos = els.getListenerInfoFor(root);
  is(infos.length, 0, "Element shouldn't have listeners (1)");

  var listenerSource = 'handlerCalled = true;';
  root.setAttribute("onclick", listenerSource);
  infos = els.getListenerInfoFor(root);
  is(infos.length, 1, "Element should have listeners (1)");
  is(infos[0].toSource(), 'function onclick(event) {\n' + listenerSource + '\n}',
     "Unexpected serialization (1)");
  is(infos[0].type, "click", "Wrong type (1)");
  is(infos[0].capturing, false, "Wrong phase (1)");
  is(infos[0].allowsUntrusted, true, "Should allow untrusted events (1)");
  is(SpecialPowers.unwrap(infos[0].listenerObject), root.onclick,
     "Should have the right listener object (1)");

  // Test disabling and enabling the listener.
  ok(!handlerCalled);
  root.click();
  ok(handlerCalled);

  clearListenerStates()
  infos[0].enabled = false;
  root.click();
  ok(!handlerCalled);

  clearListenerStates()
  infos[0].enabled = true;
  root.click();
  ok(handlerCalled);
  clearListenerStates();

  function capturingListener() {
    capturingListenerCalled = true;
  }
  function bubblingListener() {
    bubblingListenerCalled = true;
  }
  root.addEventListener("click", capturingListener, true);
  root.addEventListener("click", bubblingListener);
  root.addEventListener("fooevent", capturingListener, true);
  root.addEventListener("fooevent", bubblingListener);
  infos = els.getListenerInfoFor(root);
  // Use a child node to dispatch events so that both capturing and bubbling
  // listeners get called.
  l2 = document.getElementById("testlevel2");
  l2.click();
  ok(handlerCalled);
  ok(capturingListenerCalled);
  ok(bubblingListenerCalled);
  clearListenerStates();

  infos[0].enabled = false;
  l2.click();
  ok(!handlerCalled);
  ok(capturingListenerCalled);
  ok(bubblingListenerCalled);
  clearListenerStates();
  infos[0].enabled = true;

  infos[1].enabled = false;
  l2.click();
  ok(handlerCalled);
  ok(!capturingListenerCalled);
  ok(bubblingListenerCalled);
  clearListenerStates();
  infos[1].enabled = true;

  infos[2].enabled = false;
  l2.click();
  ok(handlerCalled);
  ok(capturingListenerCalled);
  ok(!bubblingListenerCalled);
  clearListenerStates();
  infos[2].enabled = true;

  root.removeEventListener("click", capturingListener, true);
  root.removeEventListener("click", bubblingListener);
  root.removeEventListener("fooevent", capturingListener, true);
  root.removeEventListener("fooevent", bubblingListener);
  root.removeAttribute("onclick");

  root.setAttribute("onclick", "...invalid script...");
  SimpleTest.expectUncaughtException(true);
  infos = els.getListenerInfoFor(root);
  SimpleTest.expectUncaughtException(false);
  is(infos.length, 1);
  is(infos[0].listenerObject, null);

  root.removeAttribute("onclick");
  infos = els.getListenerInfoFor(root);
  is(infos.length, 0, "Element shouldn't have listeners (2)");

  var l = function (e) { alert(e); };
  root.addEventListener("foo", l, true, true);
  root.addEventListener("foo", l, false, false);
  infos = els.getListenerInfoFor(root);
  is(infos.length, 2, "Element should have listeners (2)");
  is(infos[0].toSource(), "(function (e) { alert(e); })",
     "Unexpected serialization (2)");
  is(infos[0].type, "foo", "Wrong type (2)");
  is(infos[0].capturing, true, "Wrong phase (2)");
  is(infos[0].allowsUntrusted, true, "Should allow untrusted events (2)");
  is(SpecialPowers.unwrap(infos[0].listenerObject), l,
     "Should have the right listener object (2)");
  is(infos[1].toSource(), "(function (e) { alert(e); })",
     "Unexpected serialization (3)");
  is(infos[1].type, "foo", "Wrong type (3)");
  is(infos[1].capturing, false, "Wrong phase (3)");
  is(infos[1].allowsUntrusted, false, "Shouldn't allow untrusted events (1)");
  is(SpecialPowers.unwrap(infos[1].listenerObject), l,
     "Should have the right listener object (3)");

  root.removeEventListener("foo", l, true);
  root.removeEventListener("foo", l);
  infos = els.getListenerInfoFor(root);
  is(infos.length, 0, "Element shouldn't have listeners (3)");

  root.onclick = l;
  infos = els.getListenerInfoFor(root);
  is(infos.length, 1, "Element should have listeners (3)");
  is(infos[0].toSource(), '(function (e) { alert(e); })',
     "Unexpected serialization (4)");
  is(infos[0].type, "click", "Wrong type (4)");
  is(infos[0].capturing, false, "Wrong phase (4)");
  is(infos[0].allowsUntrusted, true, "Should allow untrusted events (3)");
  is(SpecialPowers.unwrap(infos[0].listenerObject), l,
     "Should have the right listener object (4)");

  // Event target chain tests
  l3 = document.getElementById("testlevel3");
  var textnode = l3.firstChild;
  var chain = els.getEventTargetChainFor(textnode, true);
  ok(chain.length > 3, "Too short event target chain.");
  ok(SpecialPowers.compare(chain[0], textnode), "Wrong chain item (1)");
  ok(SpecialPowers.compare(chain[1], l3), "Wrong chain item (2)");
  ok(SpecialPowers.compare(chain[2], l2), "Wrong chain item (3)");
  ok(SpecialPowers.compare(chain[3], root), "Wrong chain item (4)");

  var hasDocumentInChain = false;
  var hasWindowInChain = false;
  for (var i = 0; i < chain.length; ++i) {
    if (SpecialPowers.compare(chain[i], document)) {
      hasDocumentInChain = true;
    } else if (SpecialPowers.compare(chain[i], window)) {
      hasWindowInChain = true;
    }
  }

  ok(hasDocumentInChain, "Should have document in event target chain!");
  ok(hasWindowInChain, "Should have window in event target chain!");

  try {
    els.getListenerInfoFor(null);
    ok(false, "Should have thrown an exception.");
  } catch (ex) {
    ok(true, "We should be still running.");
  }
  setTimeout(testAllListener, 0);
}

function dispatchTrusted(t, o) {
  SpecialPowers.dispatchEvent(window, t, new Event("testevent", o));
}

function testAllListener() {
  els = SpecialPowers.wrap(els);
  var results = [];
  var expectedResults =
    [ { target: "testlevel3", phase: 3, trusted: false },
      { target: "testlevel3", phase: 3, trusted: false },
      { target: "testlevel3", phase: 3, trusted: true },
      { target: "testlevel3", phase: 3, trusted: true },
      { target: "testlevel3", phase: 3, trusted: true }
    ];

  function allListener(e) {
    results.push({
                   target: e.target.id,
                   phase: e.eventPhase,
                   trusted: e.isTrusted
                  });
    e.stopPropagation();
  }
  function allListenerTrustedOnly(e) {
    results.push({
                   target: e.target.id,
                   phase: e.eventPhase,
                   trusted: e.isTrusted
                  });
    e.stopPropagation();
  }

  els.addListenerForAllEvents(root, allListener, false, true);
  var infos = els.getListenerInfoFor(root);
  var nullTypes = 0;
  for (var i = 0; i < infos.length; ++i) {
    if (infos[i].type == null) {
      ++nullTypes;
    }
  }
  is(nullTypes, 1, "Should have one all-event-listener!");

  els.addListenerForAllEvents(root, allListener, false, true, true);
  els.addListenerForAllEvents(root, allListenerTrustedOnly, false, false, true);
  l3.dispatchEvent(new Event("testevent", { bubbles: true, composed: true }));
  dispatchTrusted(l3, { bubbles: true, composed: true });
  els.removeListenerForAllEvents(root, allListener, false);
  els.removeListenerForAllEvents(root, allListener, false, true);
  els.removeListenerForAllEvents(root, allListenerTrustedOnly, false, true);
  // make sure removeListenerForAllEvents works.
  l3.dispatchEvent(new Event("testevent", { bubbles: true, composed : true }));
  dispatchTrusted(l3, { bubbles: true, composed: true });

  // Test the order of event listeners.
  var clickListenerCalled = false;
  var allListenerCalled = false;
  function clickListener() {
    clickListenerCalled = true;
    ok(allListenerCalled, "Should have called '*' listener before normal listener!");
  }
  function allListener2() {
    allListenerCalled = true;
    ok(!clickListenerCalled, "Shouldn't have called click listener before '*' listener!");
  }
  root.onclick = null; // Remove the listener added in earlier tests.
  root.addEventListener("click", clickListener);
  els.addListenerForAllEvents(root, allListener2, false, true);
  l3.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  root.removeEventListener("click", clickListener);
  els.removeListenerForAllEvents(root, allListener2, false);
  ok(allListenerCalled, "Should have called '*' listener");
  ok(clickListenerCalled, "Should have called click listener");

  is(results.length, expectedResults.length, "count");
  for (var i = 0; i < expectedResults.length; ++i) {
    for (var p in expectedResults[i]) {
      is(results[i][p], expectedResults[i][p], p);
    }
  }
  SimpleTest.finish();
}

SimpleTest.waitForExplicitFinish();
addLoadEvent(runTests);
</script>
</pre>
<div id="testroot">
  <div id="testlevel2">
    <div id="testlevel3">
      Test
    </div>
  </div>
</div>
</body>
</html>
