<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=508906
-->
<head>
  <title>Test for Bug 603008</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=508906">Mozilla Bug 603008</a>
<p id="display"></p>
<div id="content" style="display: none">
  
</div>
<pre id="test">
<script class="testbody" type="application/javascript;version=1.8">

/** Test for Bug 306008 - Touch* Events **/

let tests = [], testTarget, parent;

let touch = {
  id: 0,
  point: {x: 0, y: 0},
  radius: {x: 0, y: 0},
  rotation: 0,
  force: 0.5,
  target: null
}

function nextTest() {
  if (tests.length)
    SimpleTest.executeSoon(tests.shift());
}

function random() {
  return Math.floor(Math.random() * 100);
}

function checkEvent(aFakeEvent) {
  return function(aEvent) {
    is(aFakeEvent.ctrlKey, aEvent.ctrlKey, "Correct ctrlKey");
    is(aFakeEvent.altKey, aEvent.altKey, "Correct altKey");
    is(aFakeEvent.shiftKey, aEvent.shiftKey, "Correct shiftKey");
    is(aFakeEvent.metaKey, aEvent.metaKey, "Correct metaKey");
    checkTouches(aFakeEvent.touches, aEvent.touches);
    checkTouches(aFakeEvent.targetTouches, aEvent.targetTouches);
    checkTouches(aFakeEvent.changedTouches, aEvent.changedTouches);
  }
}

function checkTouches(aTouches1, aTouches2) {
  is(aTouches1.length, aTouches2.length, "Correct touches length");
  for (var i = 0; i < aTouches1.length; i++) {
    checkTouch(aTouches1[i], aTouches2[i]);
  }
}

function checkTouch(aFakeTouch, aTouch) {
  is(aFakeTouch.identifier, aTouch.identifier, "Touch has correct identifier");
  is(aFakeTouch.target, aTouch.target, "Touch has correct target");
  is(aFakeTouch.page.x, aTouch.pageX, "Touch has correct pageX");
  is(aFakeTouch.page.y, aTouch.pageY, "Touch has correct pageY");
  is(aFakeTouch.page.x + Math.round(window.mozInnerScreenX), aTouch.screenX, "Touch has correct screenX");
  is(aFakeTouch.page.y + Math.round(window.mozInnerScreenY), aTouch.screenY, "Touch has correct screenY");
  is(aFakeTouch.page.x, aTouch.clientX, "Touch has correct clientX");
  is(aFakeTouch.page.y, aTouch.clientY, "Touch has correct clientY");
  is(aFakeTouch.radius.x, aTouch.radiusX, "Touch has correct radiusX");
  is(aFakeTouch.radius.y, aTouch.radiusY, "Touch has correct radiusY");
  is(aFakeTouch.rotationAngle, aTouch.rotationAngle, "Touch has correct rotationAngle");
  is(aFakeTouch.force, aTouch.force, "Touch has correct force");
}

function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) {
  var ids = [], xs=[], ys=[], rxs = [], rys = [],
      rotations = [], forces = [];

  for (var touchType of ["touches", "changedTouches", "targetTouches"]) {
    for (var i = 0; i < aEvent[touchType].length; i++) {
      if (ids.indexOf(aEvent[touchType][i].identifier) == -1) {
        ids.push(aEvent[touchType][i].identifier);
        xs.push(aEvent[touchType][i].page.x);
        ys.push(aEvent[touchType][i].page.y);
        rxs.push(aEvent[touchType][i].radius.x);
        rys.push(aEvent[touchType][i].radius.y);
        rotations.push(aEvent[touchType][i].rotationAngle);
        forces.push(aEvent[touchType][i].force);
      }
    }
  }
  return windowUtils.sendTouchEvent(aType,
                                    ids, xs, ys, rxs, rys,
                                    rotations, forces,
                                    ids.length, aModifiers, 0);
}

function touchEvent(aOptions) {
  if (!aOptions) {
    aOptions = {};
  }
  this.ctrlKey = aOptions.ctrlKey || false;
  this.altKey = aOptions.altKey || false;
  this.shiftKey = aOptions.shiftKey || false;
  this.metaKey = aOptions.metaKey || false;
  this.touches = aOptions.touches || [];
  this.targetTouches = aOptions.targetTouches || [];
  this.changedTouches = aOptions.changedTouches || [];
}

function testtouch(aOptions) {
  if (!aOptions)
    aOptions = {};
  this.identifier = aOptions.identifier || 0;
  this.target = aOptions.target || 0;
  this.page = aOptions.page || {x: 0, y: 0};
  this.radius = aOptions.radius || {x: 0, y: 0};
  this.rotationAngle = aOptions.rotationAngle || 0;
  this.force = aOptions.force || 1;
}

function testSingleTouch(name) {
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target = document.getElementById("testTarget");
  let target2 = document.getElementById("testTarget2");
  let bcr = target.getBoundingClientRect();
  let bcr2 = target2.getBoundingClientRect();

  let touch1 = new testtouch({
    page: {x: Math.round(bcr.left + bcr.width/2),
           y: Math.round(bcr.top  + bcr.height/2)},
    target: target
  });
  let event = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });

  // test touchstart event fires correctly
  var checkFunction = checkEvent(event);
  window.addEventListener("touchstart", checkFunction, false);
  sendTouchEvent(cwu, "touchstart", event, 0);
  window.removeEventListener("touchstart", checkFunction, false);

  // test touchmove event fires correctly
  event.touches[0].page.x -= 1;
  event.targetTouches[0].page.x -= 1;
  event.changedTouches[0].page.x -= 1;
  checkFunction = checkEvent(event);
  window.addEventListener("touchmove", checkFunction, false);
  sendTouchEvent(cwu, "touchmove", event, 0);
  window.removeEventListener("touchmove", checkFunction, false);

  // test touchend event fires correctly
  event.touches = [];
  event.targetTouches = [];
  checkFunction = checkEvent(event);
  window.addEventListener("touchend", checkFunction, false);
  sendTouchEvent(cwu, "touchend", event, 0);
  window.removeEventListener("touchend", checkFunction, false);

  nextTest();
}

function testSingleTouch2(name) {
  // firing a touchstart that includes only one touch will evict any touches in the queue with touchend messages
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target = document.getElementById("testTarget");
  let target2 = document.getElementById("testTarget2");
  let bcr = target.getBoundingClientRect();
  let bcr2 = target2.getBoundingClientRect();

  let touch1 = new testtouch({
    identifier: 0,
    page: {x: Math.round(bcr.left + bcr.width/2),
           y: Math.round(bcr.top  + bcr.height/2)},
    target: target
  });
  let event1 = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });
  let touch2 = new testtouch({
    identifier: 1,
    page: {x: Math.round(bcr2.left + bcr2.width/2),
           y: Math.round(bcr2.top  + bcr2.height/2)},
    target: target2
  });
  let event2 = new touchEvent({
    touches: [touch2],
    targetTouches: [touch2],
    changedTouches: [touch2]
  });

  // test touchstart event fires correctly
  var checkFunction1 = checkEvent(event1);
  window.addEventListener("touchstart", checkFunction1, false);
  sendTouchEvent(cwu, "touchstart", event1, 0);
  window.removeEventListener("touchstart", checkFunction1, false);

  event1.touches = [];
  event1.targetTouches = [];
  checkFunction1 = checkEvent(event1);
  var checkFunction2 = checkEvent(event2);

  window.addEventListener("touchend", checkFunction1, false);
  window.addEventListener("touchstart", checkFunction2, false);
  sendTouchEvent(cwu, "touchstart", event2, 0);
  window.removeEventListener("touchend", checkFunction1, false);
  window.removeEventListener("touchstart", checkFunction2, false);

  sendTouchEvent(cwu, "touchstart", event1, 0);

  nextTest();
}


function testMultiTouch(name) {
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target1 = document.getElementById("testTarget");
  let target2 = document.getElementById("testTarget2");
  let bcr = target1.getBoundingClientRect();
  let bcr2 = target2.getBoundingClientRect();

  let touch1 = new testtouch({
    identifier: 0,
    page: {x: Math.round(bcr.left + bcr.width/2),
           y: Math.round(bcr.top  + bcr.height/2)},
    target: target1
  });
  let touch2 = new testtouch({
    identifier: 1,
    page: {x: Math.round(bcr2.left + bcr2.width/2),
           y: Math.round(bcr2.top  + bcr2.height/2)},
    target: target2
  });
  let event = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });

  // test touchstart event fires correctly
  var checkFunction = checkEvent(event);
  window.addEventListener("touchstart", checkFunction, false);
  sendTouchEvent(cwu, "touchstart", event, 0);
  window.removeEventListener("touchstart", checkFunction, false);

  event.touches.push(touch2);
  event.targetTouches = [touch2];
  event.changedTouches = [touch2];
  window.addEventListener("touchstart", checkFunction, false);
  sendTouchEvent(cwu, "touchstart", event, 0);
  window.removeEventListener("touchstart", checkFunction, false);

  // test moving one touch point
  event.touches[0].page.x -= 1;
  event.targetTouches = [event.touches[0]];
  event.changedTouches = [event.touches[0]];
  window.addEventListener("touchmove", checkFunction, false);
  sendTouchEvent(cwu, "touchmove", event, 0);
  window.removeEventListener("touchmove", checkFunction, false);

  // test moving both touch points -- two touchmove events should fire, one on each target
  event.touches[0].page.x -= 1;
  event.touches[1].page.x -= 1;
  event.targetTouches = event.touches;
  event.changedTouches = event.touches;
  var touchMoveEvents = 0;
  var checkFunction2 = function(aEvent) {
    is(event.ctrlKey, aEvent.ctrlKey, "Correct ctrlKey");
    is(event.altKey, aEvent.altKey, "Correct altKey");
    is(event.shiftKey, aEvent.shiftKey, "Correct shiftKey");
    is(event.metaKey, aEvent.metaKey, "Correct metaKey");
    checkTouches(event.touches, aEvent.touches);
    checkTouches(event.changedTouches, aEvent.changedTouches);
    if (aEvent.targetTouches[0].target == target1) {
      checkTouches([event.touches[0]], aEvent.targetTouches);
    } else if (aEvent.targetTouches[0].target == target2) {
      checkTouches([event.touches[1]], aEvent.targetTouches);
    } else
      ok(false, "Event target is incorrect: " + event.targetTouches[0].target.nodeName + "#" + event.targetTouches[0].target.id);
    touchMoveEvents++;
  };
  window.addEventListener("touchmove", checkFunction2, false);
  sendTouchEvent(cwu, "touchmove", event, 0);
  ok(touchMoveEvents, 2, "Correct number of touchmove events fired");
  window.removeEventListener("touchmove", checkFunction2, false);

  // test removing just one finger
  var expected = new touchEvent({
    touches: [touch2],
    targetTouches: [],
    changedTouches: [touch1]
  });
  checkFunction = checkEvent(expected);

  event.touches = [];
  event.targetTouches = [];
  event.changedTouches = [touch1];

  // test removing the other finger
  window.addEventListener("touchend", checkFunction, false);
  sendTouchEvent(cwu, "touchend", event, 0);
  window.removeEventListener("touchend", checkFunction, false);

  event.touches = [];
  event.targetTouches = [];
  event.changedTouches = [touch2];
  checkFunction = checkEvent(event);
  window.addEventListener("touchend", checkFunction, false);
  sendTouchEvent(cwu, "touchend", event, 0);
  window.removeEventListener("touchend", checkFunction, false);

  nextTest();
}

function testTouchChanged() {
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target1 = document.getElementById("testTarget");
  let bcr = target1.getBoundingClientRect();

  let touch1 = new testtouch({
    identifier: 0,
    page: {x: Math.round(bcr.left + bcr.width/2),
           y: Math.round(bcr.top  + bcr.height/2)},
    target: target1
  });
  let event = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });

  var checkFunction = checkEvent(event);
  sendTouchEvent(cwu, "touchstart", event, 0);

  var moveEvents = 0;
  function onMove(aEvent) {
    moveEvents++;
  }

  window.addEventListener("touchmove", onMove, false);

  // the first touchmove should always fire an event
  sendTouchEvent(cwu, "touchmove", event, 0);

  // changing nothing should not fire a touchmove event
  sendTouchEvent(cwu, "touchmove", event, 0);

  // test moving x
  event.touches[0].page.x -= 1;
  sendTouchEvent(cwu, "touchmove", event, 0);

  // test moving y
  event.touches[0].page.y -= 1;
  sendTouchEvent(cwu, "touchmove", event, 0);

  // test changing y radius
  event.touches[0].radius.y += 1;
  sendTouchEvent(cwu, "touchmove", event, 0);
  
  // test changing x radius
  event.touches[0].radius.x += 1;
  sendTouchEvent(cwu, "touchmove", event, 0);

  // test changing rotationAngle
  event.touches[0].rotationAngle += 1;
  sendTouchEvent(cwu, "touchmove", event, 0);

  // test changing force
  event.touches[0].force += 1;
  sendTouchEvent(cwu, "touchmove", event, 0);

  // changing nothing again
  sendTouchEvent(cwu, "touchmove", event, 0);
  
  is(moveEvents, 7, "Six move events fired");

  window.removeEventListener("touchmove", onMove, false);
  sendTouchEvent(cwu, "touchend", event, 0);
  nextTest();
}

function testPreventDefault() {
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target = document.getElementById("testTarget");
  let target2 = document.getElementById("testTarget2");
  let bcr = target.getBoundingClientRect();
  let bcr2 = target2.getBoundingClientRect();

  let touch1 = new testtouch({
    page: {x: bcr.left + bcr.width/2,
           y: bcr.top + bcr.height/2},
    target: target
  });
  let event = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });

  let preventFunction = function(aEvent) {
    aEvent.preventDefault();
  }
  
  let tests = [
    [{ name: "touchstart", prevent: false },
     { name: "touchmove", prevent: false },
     { name: "touchmove", prevent: false },
     { name: "touchend", prevent: false }],
    [{ name: "touchstart", prevent: true, doPrevent: true },
     { name: "touchmove", prevent: false },
     { name: "touchmove", prevent: false },
     { name: "touchend", prevent: false }],
    [{ name: "touchstart", prevent: false },
     { name: "touchmove", prevent: true, doPrevent: true },
     { name: "touchmove", prevent: false },
     { name: "touchend", prevent: false }],
    [{ name: "touchstart", prevent: false },
     { name: "touchmove", prevent: false },
     { name: "touchmove", prevent: false, doPrevent: true },
     { name: "touchend", prevent: false }],
    [{ name: "touchstart", prevent: false },
     { name: "touchmove", prevent: false },
     { name: "touchmove", prevent: false },
     { name: "touchend", prevent: true, doPrevent: true }]
  ];

  var dotest = function(aTest) {
    if (aTest.doPrevent) {
      target.addEventListener(aTest.name, preventFunction, false);
    }

    if (aTest.name == "touchmove") {
      touch1.page.x++;
      event.touches[0] = touch1;
    }

    is(sendTouchEvent(cwu, aTest.name, event, 0), aTest.prevent, "Got correct status");

    if (aTest.doPrevent)
      target.removeEventListener(aTest.name, preventFunction, false);
  }

  for (var i = 0; i < tests.length; i++) {
    for (var j = 0; j < tests[i].length; j++) {
      dotest(tests[i][j]);
    }
  } 

  nextTest();
}

function testRemovingElement() {
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target = document.getElementById("testTarget");
  let bcr = document.getElementById("testTarget").getBoundingClientRect();

  let touch1 = new testtouch({
    page: {x: bcr.left + bcr.width/2,
           y: bcr.top + bcr.height/2},
  });
  let e = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });

  var touchEvents = 0;  
  var removeTarget = function(aEvent) {
    aEvent.target.parentNode.removeChild(aEvent.target);
  };

  var checkTarget = function(aEvent) {
    is(aEvent.target, target, "Event has correct target");
    touchEvents++;
  };

  target.addEventListener("touchstart", removeTarget, false);
  target.addEventListener("touchmove", checkTarget, false);
  target.addEventListener("touchend", checkTarget, false);

  sendTouchEvent(cwu, "touchstart", e, 0);

  e.touches[0].page.x++;
  sendTouchEvent(cwu, "touchmove", e, 0);
  sendTouchEvent(cwu, "touchend", e, 0);

  target.removeEventListener("touchstart", removeTarget, false);
  target.removeEventListener("touchmove", checkTarget, false);
  target.removeEventListener("touchend", checkTarget, false);

  is(touchEvents, 2, "Check target was called twice");

  nextTest();
}

function testNAC() {
  let cwu = SpecialPowers.getDOMWindowUtils(window);
  let target = document.getElementById("testTarget3");
  let bcr = target.getBoundingClientRect();

  let touch1 = new testtouch({
    page: {x: Math.round(bcr.left + bcr.width/2),
           y: Math.round(bcr.top  + bcr.height/2)},
    target: target
  });
  let event = new touchEvent({
    touches: [touch1],
    targetTouches: [touch1],
    changedTouches: [touch1]
  });

  // test touchstart event fires correctly
  var checkFunction = checkEvent(event);
  window.addEventListener("touchstart", checkFunction, false);
  sendTouchEvent(cwu, "touchstart", event, 0);
  window.removeEventListener("touchstart", checkFunction, false);

  sendTouchEvent(cwu, "touchend", event, 0);

  nextTest();
}

function doTest() {
  tests.push(testSingleTouch);
  tests.push(testSingleTouch2);
  tests.push(testMultiTouch);
  tests.push(testPreventDefault);
  tests.push(testTouchChanged);
  tests.push(testRemovingElement);
  tests.push(testNAC);

  tests.push(function() {
    SimpleTest.finish();
  });

  nextTest();
}

SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);

</script>
</pre>
<div id="parent">
  <span id="testTarget" style="padding: 5px; border: 1px solid black;">testTarget</span>
  <span id="testTarget2" style="padding: 5px; border: 1px solid blue;">testTarget</span>
  <input type="text" id="testTarget3">
</div>
</body>
</html>