<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=572270
-->
<head>
  <title>Test TimeEvents dispatching</title>
  <script type="text/javascript" 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=572270">Mozilla Bug
  572270</a>
<p id="display"></p>
<div id="content" style="display: none">
<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px">
  <g font-size="10px">
    <circle cx="0" cy="0" r="15" fill="blue" id="circle"
      onbegin="parentHandler(evt)" onrepeat="parentHandler(evt)"
      onend="parentHandler(evt)">
      <animate attributeName="cy" from="0" to="100" dur="60s" begin="2s"
        id="anim" repeatCount="2"
        onbegin="handleOnBegin(evt)" onrepeat="handleOnRepeat(evt)"
        onend="handleOnEnd(evt)"/>
    </circle>
  </g>
</svg>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
<![CDATA[
/** Test SMIL TimeEvents dispatching **/

/* Global Variables */
const gTimeoutDur = 60000; // Time until we give up waiting for events in ms
var gSvg    = document.getElementById("svg");
var gAnim   = document.getElementById('anim');
var gCircle = document.getElementById('circle');
var gExpectedEvents = new Array();
var gTimeoutID;
var gTestStages =
  [ testPlaybackBegin,
    testPlaybackRepeat,
    testPlaybackEnd,
    testForwardsSeekToMid,
    testForwardsSeekToNextInterval,
    testForwardsSeekPastEnd,
    testBackwardsSeekToMid,
    testBackwardsSeekToStart,
    testCreateEvent,
    testRegistration
    ];

SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("untriaged");

function continueTest()
{
  if (gTestStages.length == 0) {
    SimpleTest.finish();
    return;
  }
  gTestStages.shift()();
}

function testPlaybackBegin()
{
  // Test events are dispatched through normal playback
  gSvg.pauseAnimations();
  gSvg.setCurrentTime(1.99);
  gExpectedEvents.push("beginEvent", "beginEvent"); // Two registered handlers
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testPlaybackRepeat()
{
  gSvg.pauseAnimations();
  gSvg.setCurrentTime(61.99);
  gExpectedEvents.push(["repeatEvent", 1], ["repeatEvent", 1]);
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testPlaybackEnd()
{
  gSvg.pauseAnimations();
  gSvg.setCurrentTime(121.99);
  gExpectedEvents.push("endEvent", "endEvent");
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testForwardsSeekToMid()
{
  gSvg.pauseAnimations();
  // Set animation parameters to something that repeats a lot
  gSvg.setCurrentTime(0);
  gAnim.setAttribute('begin', '2s; 102s');
  gAnim.setAttribute('dur', '15s');
  gAnim.setAttribute('repeatCount', '6');
  gSvg.setCurrentTime(46.99);
  gExpectedEvents.push("beginEvent", "beginEvent",
                       ["repeatEvent", 3], ["repeatEvent", 3]);
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testForwardsSeekToNextInterval()
{
  // Skip to next interval -- we shouldn't get any additional begin or end
  // events in between
  gSvg.pauseAnimations();
  gSvg.setCurrentTime(131.99);
  gExpectedEvents.push(["repeatEvent", 2], ["repeatEvent", 2]);
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testForwardsSeekPastEnd()
{
  gSvg.pauseAnimations();
  gSvg.setCurrentTime(200);
  gExpectedEvents.push("endEvent", "endEvent");
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testBackwardsSeekToMid()
{
  gSvg.pauseAnimations();
  gSvg.setCurrentTime(31.99);
  gExpectedEvents.push("beginEvent", "beginEvent",
                       ["repeatEvent", 2], ["repeatEvent", 2]);
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function testBackwardsSeekToStart()
{
  gSvg.pauseAnimations();
  gExpectedEvents.push("endEvent", "endEvent");
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.setCurrentTime(0);
}

function testCreateEvent()
{
  var evt;
  try {
    evt = document.createEvent("TimeEvents");
  } catch (e) {
    ok(false, "Failed to create TimeEvent via script: " + e);
    return;
  }
  evt.initTimeEvent("repeatEvent", null, 3);
  is(evt.type, "repeatEvent", "Unexpected type for user-generated event");
  is(evt.detail, 3, "Unexpected detail for user-generated event");
  is(evt.target, null, "Unexpected event target");
  is(evt.currentTarget, null, "Unexpected event current target");
  is(evt.eventPhase, evt.NONE);
  is(evt.bubbles, false, "Event should not bubble");
  is(evt.cancelable, false, "Event should not be cancelable");
  is(evt.view, null, "Event view should be null");

  // Prior to dispatch we should be able to change the event type
  evt.initTimeEvent("beginEvent", document.defaultView, 0);
  is(evt.type, "beginEvent", "Failed to update event type before dispatch");
  is(evt.detail, 0, "Failed to update event detail before dispatch");
  is(evt.view, document.defaultView, "Event view should be set");

  // But not directly as it's readonly
  try {
    evt.type = "endEvent";
  } catch(e) { }
  is(evt.type, "beginEvent", "Event type should be readonly");

  // Likewise the detail field should be readonly
  try {
    evt.detail = "8";
  } catch(e) { }
  is(evt.detail, 0, "Event detail should be readonly");

  // Dispatch
  gExpectedEvents.push("beginEvent", "beginEvent");
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gAnim.dispatchEvent(evt);
}

function testRegistration()
{
  gSvg.pauseAnimations();
  // Reset animation to something simple
  gSvg.setCurrentTime(0);
  gAnim.setAttribute('begin', '2s');
  gAnim.setAttribute('dur', '50s');

  // Remove attribute handler
  gAnim.removeAttribute('onbegin');

  // Add bogus handlers
  gAnim.setAttribute('onbeginElement', 'handleOnBegin(evt)');
  gAnim.addEventListener("begin", handleOnBegin, false);
  gAnim.addEventListener("onbegin", handleOnBegin, false);

  // We should now have just one legitimate listener: the one registered to
  // handle 'beginElement'
  gSvg.setCurrentTime(1.99);
  gExpectedEvents.push("beginEvent");
  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
  gSvg.unpauseAnimations();
}

function handleOnBegin(evt)
{
  is(evt.type, "beginEvent", "Expected begin event but got " + evt.type);
  checkExpectedEvent(evt);
}

function handleOnRepeat(evt)
{
  is(evt.type, "repeatEvent", "Expected repeat event but got " + evt.type);
  checkExpectedEvent(evt);
}

function handleOnEnd(evt)
{
  is(evt.type, "endEvent", "Expected end event but got " + evt.type);
  checkExpectedEvent(evt);
}

function sanityCheckEvent(evt)
{
  is(evt.target, gAnim, "Unexpected event target");
  is(evt.currentTarget, gAnim, "Unexpected event current target");
  is(evt.eventPhase, evt.AT_TARGET);
  is(evt.bubbles, false, "Event should not bubble");
  is(evt.cancelable, false, "Event should not be cancelable");
  if (SpecialPowers.getBoolPref("dom.event.highrestimestamp.enabled")) {
    var now = window.performance.now();
    ok(evt.timeStamp > 0 && evt.timeStamp < now,
       "Event timeStamp (" + evt.timeStamp + ") should be > 0 but " +
       "before the current time (" + now + ")");
  } else {
    is(evt.timeStamp, 0, "Event timeStamp should be 0");
  }
  ok(evt.view !== null, "Event view not set");
}

function checkExpectedEvent(evt)
{
  sanityCheckEvent(evt);
  ok(gExpectedEvents.length > 0, "Unexpected event: " + evt.type);
  if (gExpectedEvents.length == 0) return;

  var expected = gExpectedEvents.shift();
  if (typeof expected == 'string') {
    is(evt.type, expected, "Unexpected event type");
    is(evt.detail, 0, "Unexpected event detail (repeat iteration)");
  } else {
    is(evt.type, expected[0], "Unexpected event type");
    is(evt.detail, expected[1], "Unexpected event detail (repeat iteration)");
  }
  if (gExpectedEvents.length == 0) {
    clearTimeout(gTimeoutID);
    continueTest();
  }
}

function timeoutFail()
{
  ok(false, "Timed out waiting for events: " + gExpectedEvents.join(', '));
  SimpleTest.finish(); // No point continuing
}

function parentHandler(evt)
{
  ok(false, "Handler on parent got called but event shouldn't bubble.");
}

window.addEventListener("load", continueTest, false);

// Register event handlers *in addition* to the handlers already added via the
// "onbegin", "onend", "onrepeat" attributes on the <animate> and <circle>
// elements. This is to test that both types of registration work.
gAnim.addEventListener("beginEvent", handleOnBegin, false);
gAnim.addEventListener("repeatEvent", handleOnRepeat, false);
gAnim.addEventListener("endEvent", handleOnEnd, false);
gCircle.addEventListener("beginEvent", parentHandler, false);

var expectedEvents =
  ["begin", "beginEvent", "repeat", "repeatEvent", "end", "endEvent", "SVGZoom", "zoom"];

for (var i = 0; i < expectedEvents.length; ++i) {
  is((new Event(expectedEvents[i])).type, expectedEvents[i], "Unexpected event type!");
}

var timeEvents = ["begin", "repeat", "end"];
var expectedEvents = ["begin", "beginEvent", "repeat", "repeatEvent", "end", "endEvent"];
var d = document.createElement("div");
for (var i = 0; i < timeEvents.length; ++i) {
  d.addEventListener(timeEvents[i], function(e) {
    is(e.type, expectedEvents[0], "Got the expected event type.");
    expectedEvents.shift();
  });

  // Without "Event" suffix.
  var e = document.createEvent("timeevent");
  e.initEvent(timeEvents[i], true, true);
  d.dispatchEvent(e);

  // With "Event" suffix.
  e = document.createEvent("timeevent");
  e.initEvent(timeEvents[i] + "Event", true, true);
  d.dispatchEvent(e);
}
is(expectedEvents.length, 0, "Got all the expected events.");

expectedEvents = ["zoom", "SVGZoom"];
d.addEventListener("zoom", function(e) {
  is(e.type, expectedEvents[0]);
  expectedEvents.shift();
});

var zoomEvent = document.createEvent("svgzoomevent");
zoomEvent.initEvent("zoom", true, true);
d.dispatchEvent(zoomEvent);
zoomEvent = document.createEvent("svgzoomevent");
zoomEvent.initEvent("SVGZoom", true, true);
d.dispatchEvent(zoomEvent);
is(expectedEvents.length, 0, "Got all the expected events.");

]]>
</script>
</pre>
</body>
</html>