<meta name="variant" content="?mouse">
<meta name="variant" content="?touch">
<meta name="variant" content="?pen">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script type="text/javascript" src="pointerevent_support.js"></script>
<style>
iframe {
  width: 300px;
  height: 300px;
  top: 100px;
  left: 100px;
  border: 0;
  position: absolute;
  background: green;
}
#outerFrame {
  width: 500px;
  height: 500px;
  background: blue;
}
body {
  touch-action:none;
}
</style>
<body id="outerFrame body" onload="run()">
<div id='outerFrame'>
<iframe id='innerFrameElement' src="resources/pointerevent_pointercapture-iframe.html"></iframe>
</div>
</body>
<script>
var receivedEventList = [];
// |start_logging| is set to true in the pointerdown event handler, and as such
// also tracks if pointerdown happened already in the test.
var start_logging = false;
var first_pointermove_happened = false;

// Add a timeout promise when waiting for lostpointercapture and pointerup.
// We add this to make it easy to know which event wasn't fired on the right
// element.
var eventTimeout = ()=>(()=>new Promise((resolve, reject)=>{
    const msToWait = 4000;
    let start;
    function wait(timestamp){
        if(start === undefined)
            start = timestamp;
        if(timestamp - start < msToWait)
            requestAnimationFrame(wait);
        else
            resolve();
    }
    requestAnimationFrame(wait);
}));


function handleEvent(event) {
  if (event.type == 'pointerdown') {
    start_logging = true;
    if (document.setPointerCaptureOnPointerDown) {
      event.target.setPointerCapture(event.pointerId);
    }
  }

  // Only log the first pointermove event after pointerdown. We need to account
  // for coalesced pointermove events and for the pointermove events that
  // happen after lostpointercapture.
  if (start_logging && (event.type !== "pointermove" || !first_pointermove_happened))
    receivedEventList.push(event.target.id + ' received ' + event.type);

  if (event.type == "pointermove"){
    if (document.releasePointerCaptureOnFirstMove && event.target.hasPointerCapture(event.pointerId))
        event.target.releasePointerCapture(event.pointerId);
    if(start_logging)
        first_pointermove_happened = true;
  }
};

document.testEventList = ['pointerup', 'pointerdown', 'pointermove', 'gotpointercapture', 'lostpointercapture'];
document.testEventList.forEach(function(eventName) {
  document.getElementById('outerFrame').addEventListener(eventName, handleEvent);
});

function Reset() {
  document.setPointerCaptureOnPointerDown = false;
  document.releasePointerCaptureOnFirstMove = false;
  receivedEventList = [];
  start_logging = false;
  first_pointermove_happened = false;
}

function run() {
    var pointerType = location.search.substring(1);
    promise_test (async(t) => {
        Reset();
        document.setPointerCaptureOnPointerDown = true;
        expectedEventList = ["innerFrame received pointerdown",
                             "innerFrame received gotpointercapture",
                             "innerFrame received pointermove",
                             "innerFrame received pointerup",
                             "innerFrame received lostpointercapture"];
        var pointerId = pointerType + "Pointer1";

        var innerFrame = document.getElementById('innerFrameElement');
        var innerFrameDocument =  innerFrame.contentDocument;
        // We are interested in tracking events only after pointerdown
        var pointerdown_happened = new Promise((resolve, reject)=>{innerFrameDocument.addEventListener("pointerdown",resolve);});
        var watcher_promise = pointerdown_happened.then(()=>{
            var watch_inner_frame = new EventWatcher(t, innerFrameDocument, ["lostpointercapture"], eventTimeout());
            return watch_inner_frame.wait_for(["lostpointercapture"]);
        });

        await new test_driver.Actions()
                             .addPointer(pointerId, pointerType)
                             .pointerMove(200, 200)
                             .pointerDown()
                             .pointerMove(150, 150)
                             .pointerMove(50, 50)
                             .pointerUp()
                             .pointerMove(75, 75)
                             .send();
        // Wait for lostpointercapture to fire.
        await watcher_promise;
        assert_array_equals(receivedEventList, expectedEventList, "Received events: " + receivedEventList);
        document.setPointerCaptureOnPointerDown = false;
    }, "Test " + pointerType + "pointer capture in same-origin frame: Pointer down at inner frame and set pointer capture.");

    promise_test (async(t) => {
        Reset();
        document.setPointerCaptureOnPointerDown = true;
        expectedEventList = ["outerFrame received pointerdown",
                             "outerFrame received gotpointercapture",
                             "outerFrame received pointermove",
                             "outerFrame received pointerup",
                             "outerFrame received lostpointercapture"];
        var pointerId = pointerType + "Pointer1";

        // We are interested in tracking events only after pointerdown
        var pointerdown_happened = new Promise((resolve, reject)=>{document.getElementById('outerFrame').addEventListener("pointerdown",resolve);});
        var watcher_promise = pointerdown_happened.then(()=>{
            // For this test we're going to wait for both pointerup and lostpointercapture,
            // as pointerup fires on innerFrame on Windows (see crbug.com/1186788)
            var watch_outer_frame_for_pointerup = new EventWatcher(t, document.getElementById('outerFrame'), ["pointerup"], eventTimeout());
            return watch_outer_frame_for_pointerup.wait_for(["pointerup"]).then(()=>{
                var watch_outer_frame_for_lostpointercapture = new EventWatcher(t, document.getElementById('outerFrame'), ["lostpointercapture"], eventTimeout());
                return watch_outer_frame_for_lostpointercapture.wait_for(["lostpointercapture"]);
            });
        });

        await new test_driver.Actions()
                             .addPointer(pointerId, pointerType)
                             .pointerMove(25, 25)
                             .pointerDown()
                             .pointerMove(200, 200)
                             .pointerUp()
                             .pointerMove(25, 25)
                             .send();
        // Wait for lostpointercapture to fire.
        await watcher_promise;
        assert_array_equals(receivedEventList, expectedEventList, "Received events: " + receivedEventList);
        document.setPointerCaptureOnPointerDown = false;
    }, "Test " + pointerType + "pointer capture in same-origin frame: Pointer down at outer frame body and set pointer capture.");


    promise_test (async(t) => {
        Reset();
        document.setPointerCaptureOnPointerDown = true;
        document.releasePointerCaptureOnFirstMove = true;
        // Mouse event has the frame capture, so after pointer capture released, events are
        // dispatched to innerFrameDocument.
        expectedEventList = ["innerFrame received pointerdown",
                             "innerFrame received gotpointercapture",
                             "innerFrame received pointermove",
                             "innerFrame received lostpointercapture",
                             (pointerType == "touch" ? "outerFrame": "innerFrameDocument") + " received pointerup",];
        var pointerId = pointerType + "Pointer1";

        var innerFrame = document.getElementById('innerFrameElement');
        var innerFrameDocument =  innerFrame.contentDocument;
        // We are interested in tracking events only after pointerdown
        var pointerdown_happened = new Promise((resolve, reject)=>{innerFrameDocument.addEventListener("pointerdown",resolve);});
        var watcher_promise = pointerdown_happened.then(()=>{
            if(pointerType === "touch"){
                var watch_outer_frame = new EventWatcher(t, document.getElementById('outerFrame'), ["pointerup"], eventTimeout());
                return watch_outer_frame.wait_for("pointerup");
            }else{
                var watch_inner_frame = new EventWatcher(t, innerFrameDocument, ["pointerup"], eventTimeout());
                return watch_inner_frame.wait_for("pointerup");
            }
        });

        await new test_driver.Actions()
                             .addPointer(pointerId, pointerType)
                             .pointerMove(200, 200)
                             .pointerDown()
                             .pointerMove(150, 150)
                             .pointerMove(50, 50)
                             .pointerUp()
                             .pointerMove(150, 150)
                             .send();
        // Wait for pointerup to fire.
        await watcher_promise;
        assert_array_equals(receivedEventList, expectedEventList, "Received events: " + receivedEventList);
        document.releasePointerCaptureOnFirstMove = false;
        document.setPointerCaptureOnPointerDown = false;
    }, "Test " + pointerType + "pointer capture in same-origin frame: Pointerdown with set capture at inner frame, then release on next pointermove.");


    promise_test (async(t) => {
        Reset();
        document.setPointerCaptureOnPointerDown = true;
        document.releasePointerCaptureOnFirstMove = true;
        expectedEventList = ["outerFrame received pointerdown",
                             "outerFrame received gotpointercapture",
                             "outerFrame received pointermove",
                             "outerFrame received lostpointercapture",
                             "innerFrame received pointerup"];
        var pointerId = pointerType + "Pointer1";

        var innerFrame = document.getElementById('innerFrameElement');
        var innerFrameDocument =  innerFrame.contentDocument;
        // We are interested in tracking events only after pointerdown
        var pointerdown_happened = new Promise((resolve, reject)=>{document.getElementById('outerFrame').addEventListener("pointerdown",resolve);});
        var watcher_promise = pointerdown_happened.then(()=>{
            var watch_inner_frame = new EventWatcher(t, innerFrameDocument, ["pointerup"], eventTimeout());
            return watch_inner_frame.wait_for(["pointerup"]);
        });

        await new test_driver.Actions()
                             .addPointer(pointerId, pointerType)
                             .pointerMove(50, 50)
                             .pointerDown()
                             .pointerMove(200, 200)
                             // Pause here to make sure that the previous and following pointer moves
                             // are not coalesced. If they are coalesced, we will not see the second
                             // move event which is when the pending lostpointercapture should be fired
                             // (Since the pointerup event is targeted at a different frame, it won't dispatch
                             // the pending lostpointercapture event).
                             .pause(300)
                             .pointerMove(250, 250)
                             .pointerUp()
                             .send();
        // Wait for pointerup to fire.
        await watcher_promise;
        assert_array_equals(receivedEventList, expectedEventList, "Received events: " + receivedEventList);
        document.releasePointerCaptureOnFirstMove = false;
        document.setPointerCaptureOnPointerDown = false;
    }, "Test " + pointerType + "pointer capture in same-origin frame: Pointerdown with set capture at outer frame, then release on next pointermove.");
}
</script>

</body>
