<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>Test for Multiple Touches</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/EventUtils.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="target0" style="width: 100px; height: 100px; background: green"></div>
<div id="target1" style="width: 100px; height: 100px; background: red"></div>
<script type="text/javascript">
// TODO: We should probably make EventUtils.js to support multiple touch.
// Currently the use case is simple, so we just add support here.
// Once we have more use cases, we could come out a more generic way to
// support it.
var touches = {
  ids: [],
  lefts: [],
  tops: [],
  rxs: [],
  rys: [],
  angles: [],
  forces: [],
  tiltXs: [],
  tiltYs: [],
  twists: [],
};

function synthesizeTouchEvent(aType, aTouches) {
  var utils = _getDOMWindowUtils(window);
  if (!utils) {
    ok(false, "unable to get nsIDOMWindowUtils");
    return;
  }

  utils.sendTouchEvent(aType, aTouches.ids, aTouches.lefts, aTouches.tops,
                       aTouches.rxs, aTouches.rys, aTouches.angles,
                       aTouches.forces, aTouches.tiltXs, aTouches.tiltYs,
                       aTouches.twists, 0 /* modifiers */);
}

function synthesizeTouchStart(aTarget, aId, aOffsetX, aOffsetY) {
  if (touches.ids.some((aElem) => { return aElem === aId; })) {
    ok(false, `touch with id=${aTouch.id} is already registered`);
    return;
  }

  let rect = aTarget.getBoundingClientRect();
  touches.ids.push(aId);
  touches.lefts.push(rect.left + aOffsetX);
  touches.tops.push(rect.top + aOffsetY);
  touches.rxs.push(1);
  touches.rys.push(1);
  touches.angles.push(0);
  touches.forces.push(1);
  touches.tiltXs.push(0);
  touches.tiltYs.push(0);
  touches.twists.push(0);

  synthesizeTouchEvent("touchstart", touches);
}

function synthesizeTouchEnd(aTarget, aId, aOffsetX, aOffsetY) {
  let index = touches.ids.indexOf(aId);
  if (-1 === index) {
    ok(false, `touch with id=${aTouch.id} isn't registered`);
    return;
  }

  var removedTouches = {
    ids: touches.ids.splice(index, 1),
    lefts: touches.lefts.splice(index, 1),
    tops: touches.tops.splice(index, 1),
    rxs: touches.rxs.splice(index, 1),
    rys: touches.rys.splice(index, 1),
    angles: touches.angles.splice(index, 1),
    forces: touches.forces.splice(index, 1),
    tiltXs: touches.tiltXs.splice(index, 1),
    tiltYs: touches.tiltYs.splice(index, 1),
    twists: touches.twists.splice(index, 1),
  };

  synthesizeTouchEvent("touchend", removedTouches);
}

function synthesizeTouchMove(aTarget, aId, aOffsetX, aOffsetY) {
  let index = touches.ids.indexOf(aId);
  if (-1 === index) {
    ok(false, `touch with id=${aTouch.id} isn't registered`);
    return;
  }

  let rect = aTarget.getBoundingClientRect();
  touches.lefts[index] = rect.left + aOffsetX;
  touches.tops[index] = rect.top + aOffsetY;

  synthesizeTouchEvent("touchmove", touches);
}

var target0 = document.getElementById("target0");
var target1 = document.getElementById("target1");

function WaitExpectedEvents(aListenEvents, aExpectedEvents, aEventGenerator) {
  let promise = new Promise(function(aResolve) {
    let index = 0;
    let checkReceivedEvents = function(aEvent) {
      if (aExpectedEvents.length === 0) {
        ok(false, `receive unexpected ${aEvent.type} event from ${aEvent.target}`);
        return;
      }
      index++;
      let expectedResult = aExpectedEvents.shift();
      isDeeply(expectedResult, [aEvent.target, aEvent.type], `${index}. expect receive ${expectedResult[1]} event from ${expectedResult[0]}`);
      if (aExpectedEvents.length === 0) {
        // Wait a bit to see if there is any additional unexpected event fired.
        setTimeout(function() {
          // Clean up
          aListenEvents.forEach((aElem) => {
            target0.removeEventListener(aElem, checkReceivedEvents);
            target1.removeEventListener(aElem, checkReceivedEvents);
          });
          aResolve();
        }, 0);
      }
    };

    aListenEvents.forEach((aElem) => {
      target0.addEventListener(aElem, checkReceivedEvents);
      target1.addEventListener(aElem, checkReceivedEvents);
    });
  });

  aEventGenerator();

  return promise;
}

// eslint-disable-next-line mozilla/no-addtask-setup
add_task(async function setup() {
  await SimpleTest.promiseFocus();
  await SpecialPowers.pushPrefEnv({
    set: [["dom.w3c_pointer_events.implicit_capture", false]]
  });
});

// Test for bug 1521082
add_task(async function ShouldNotSendDuplicatedPointerDown() {
  return WaitExpectedEvents(
    ["pointerup", "pointerdown"],
    [ // [event target, event type]
      [target0, "pointerdown"],
      [target1, "pointerdown"],
      [target1, "pointerup"],
      [target0, "pointerup"],
    ],
    function() {
      var defaultId = SpecialPowers.Ci.nsIDOMWindowUtils.DEFAULT_TOUCH_POINTER_ID;
      synthesizeTouchStart(target0, defaultId, 10, 10);
      synthesizeTouchStart(target1, defaultId + 1, 10, 10);
      synthesizeTouchEnd(target1, defaultId + 1, 10, 10);
      synthesizeTouchEnd(target0, defaultId, 10, 10);
    }
  );
});

// Test for bug 1323400
add_task(async function ShouldNotSendDuplicatedPointerMove() {
  return WaitExpectedEvents(
    ["pointerup", "pointerdown","pointermove"],
    [ // [event target, event type]
      [target0, "pointerdown"],
      [target1, "pointerdown"],
      [target1, "pointermove"],
      // Should receive only one pointer event for target 1.
      [target1, "pointermove"],
      [target1, "pointerup"],
      [target0, "pointerup"],
    ],
    function() {
      var defaultId = SpecialPowers.Ci.nsIDOMWindowUtils.DEFAULT_TOUCH_POINTER_ID;
      synthesizeTouchStart(target0, defaultId, 10, 10);
      synthesizeTouchStart(target1, defaultId + 1, 10, 10);
      synthesizeTouchMove(target1, defaultId + 1, 11, 11);
      synthesizeTouchMove(target1, defaultId + 1, 12, 12);
      synthesizeTouchEnd(target1, defaultId + 1, 10, 10);
      synthesizeTouchEnd(target0, defaultId, 10, 10);
    }
  );
});

</script>
</body>
</html>
