const All_Pointer_Events = [ "pointerdown", "pointerup", "pointercancel", "pointermove", "pointerover", "pointerout", "pointerenter", "pointerleave", "gotpointercapture", "lostpointercapture"]; // https://w3c.github.io/pointerevents/#the-button-property // Values for the button property, which indicates the device button whose state // change fired the event. const ButtonChange = { NONE: -1, PEN_CONTACT: 0, TOUCH_CONTACT: 0, LEFT_MOUSE: 0, MIDDLE_MOUSE: 1, RIGHT_MOUSE: 2, X1_MOUSE: 3, X2_MOUSE: 4, PEN_ERASER_BUTTON: 5 }; // https://w3c.github.io/pointerevents/#the-buttons-property // The buttons property gives the current state of the device buttons as a // bitmask. const ButtonsBitfield = { NONE: 0, PEN_CONTACT: 1, TOUCH_CONTACT: 1, LEFT_MOUSE: 1, RIGHT_MOUSE: 2, PEN_BARREL_BUTTON: 2, MIDDLE_MOUSE: 4, X1_MOUSE: 8, X2_MOUSE: 16, PEN_ERASER_BUTTON: 32 }; // Check for conformance to PointerEvent interface // https://w3c.github.io/pointerevents/#pointerevent-interface function check_PointerEvent(event, testNamePrefix) { if (testNamePrefix === undefined) testNamePrefix = ""; // Use expectedPointerType if set otherwise just use the incoming event pointerType in the test name. var pointerTestName = (testNamePrefix ? testNamePrefix + ' ' : '') + (expectedPointerType == null ? event.pointerType : expectedPointerType) + ' ' + event.type; if (expectedPointerType != null) { test(function () { assert_equals(event.pointerType, expectedPointerType); }, pointerTestName + ".pointerType is correct."); } test(function () { assert_true(event instanceof event.target.ownerDocument.defaultView.PointerEvent); }, pointerTestName + " event is a PointerEvent event"); // Check attributes for conformance to WebIDL (existence, type, being readable). var idl_type_check = { "long": function (v) { return typeof v === "number" && Math.round(v) === v; }, "float": function (v) { return typeof v === "number"; }, "string": function (v) { return typeof v === "string"; }, "boolean": function (v) { return typeof v === "boolean" }, "object": function (v) { return typeof v === "object" } }; [ ["long", "pointerId"], ["float", "width"], ["float", "height"], ["float", "pressure"], ["long", "tiltX"], ["long", "tiltY"], ["string", "pointerType"], ["boolean", "isPrimary"], ["long", "detail", 0], ["object", "fromElement"], ["object", "toElement"], ["boolean", "isTrusted"], ["boolean", "composed"], ["boolean", "bubbles"] ].forEach(function (attr) { var type = attr[0]; var name = attr[1]; test(function () { // Existence check. assert_true(name in event, "attribute exists"); // Readonly check. assert_readonly(event.type, name, "attribute is readonly"); // Type check. assert_true(idl_type_check[type](event[name]), "attribute type " + type + " (JS type was " + typeof event[name] + ")"); }, pointerTestName + "." + name + " conforms to WebIDL"); }); // Check values for inherited attributes. // https://w3c.github.io/pointerevents/#attributes-and-default-actions test(function () { assert_equals(event.fromElement, null); }, pointerTestName + ".fromElement value is null"); test(function () { assert_equals(event.toElement, null); }, pointerTestName + ".toElement value is null"); test(function () { assert_equals(event.isTrusted, true); }, pointerTestName + ".isTrusted value is true"); test(function () { let expected = (event.type != 'pointerenter' && event.type != 'pointerleave'); assert_equals(event.composed, expected); }, pointerTestName + ".composed value is valid"); test(function () { let expected = (event.type != 'pointerenter' && event.type != 'pointerleave'); assert_equals(event.bubbles, expected); }, pointerTestName + ".bubbles value is valid"); // Check the pressure value. // https://w3c.github.io/pointerevents/#dom-pointerevent-pressure test(function () { assert_greater_than_equal(event.pressure, 0, "pressure is greater than or equal to 0"); assert_less_than_equal(event.pressure, 1, "pressure is less than or equal to 1"); if (event.buttons === 0) { assert_equals(event.pressure, 0, "pressure is 0 with no buttons pressed"); } else { assert_greater_than(event.pressure, 0, "pressure is greater than 0 with a button pressed"); if (event.pointerType === "mouse") { assert_equals(event.pressure, 0.5, "pressure is 0.5 for mouse with a button pressed"); } } }, pointerTestName + ".pressure value is valid"); // Check mouse-specific properties. if (event.pointerType === "mouse") { test(function () { assert_equals(event.width, 1, "width of mouse should be 1"); assert_equals(event.height, 1, "height of mouse should be 1"); assert_equals(event.tiltX, 0, event.type + ".tiltX is 0 for mouse"); assert_equals(event.tiltY, 0, event.type + ".tiltY is 0 for mouse"); assert_true(event.isPrimary, event.type + ".isPrimary is true for mouse"); }, pointerTestName + " properties for pointerType = mouse"); } // Check "pointerup" specific properties. if (event.type == "pointerup") { test(function () { assert_equals(event.width, 1, "width of pointerup should be 1"); assert_equals(event.height, 1, "height of pointerup should be 1"); }, pointerTestName + " properties for pointerup"); } } function showPointerTypes() { var complete_notice = document.getElementById("complete-notice"); var pointertype_log = document.getElementById("pointertype-log"); var pointertypes = Object.keys(detected_pointertypes); pointertype_log.innerHTML = pointertypes.length ? pointertypes.join(",") : "(none)"; complete_notice.style.display = "block"; } function showLoggedEvents() { var event_log_elem = document.getElementById("event-log"); event_log_elem.innerHTML = event_log.length ? event_log.join(", ") : "(none)"; var complete_notice = document.getElementById("complete-notice"); complete_notice.style.display = "block"; } function failOnScroll() { assert_true(false, "scroll received while shouldn't"); } function updateDescriptionNextStep() { document.getElementById('desc').innerHTML = "Test Description: Try to scroll text RIGHT."; } function updateDescriptionComplete() { document.getElementById('desc').innerHTML = "Test Description: Test complete"; } function updateDescriptionSecondStepTouchActionElement(target, scrollReturnInterval) { window.step_timeout(function() { objectScroller(target, 'up', 0);} , scrollReturnInterval); document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT moving your outside of the red border"; } function updateDescriptionThirdStepTouchActionElement(target, scrollReturnInterval, callback = null) { window.step_timeout(function() { objectScroller(target, 'left', 0); if (callback) { callback(); } }, scrollReturnInterval); document.getElementById('desc').innerHTML = "Test Description: Try to scroll element DOWN then RIGHT starting your touch inside of the element. Then tap complete button"; } function updateDescriptionFourthStepTouchActionElement(target, scrollReturnInterval) { document.getElementById('desc').innerHTML = "Test Description: Try to scroll element RIGHT starting your touch inside of the element"; } function objectScroller(target, direction, value) { if (direction == 'up') { target.scrollTop = 0; } else if (direction == 'left') { target.scrollLeft = 0; } } function sPointerCapture(e) { try { target0.setPointerCapture(e.pointerId); } catch(e) { } } function rPointerCapture(e) { try { captureButton.value = 'Set Capture'; target0.releasePointerCapture(e.pointerId); } catch(e) { } } var globalPointerEventTest = null; var expectedPointerType = null; const ALL_POINTERS = ['mouse', 'touch', 'pen']; function MultiPointerTypeTest(testName, types) { this.testName = testName; this.types = types; this.currentTypeIndex = 0; this.currentTest = null; this.createNextTest(); } MultiPointerTypeTest.prototype.step = function(op) { this.currentTest.step(op); } MultiPointerTypeTest.prototype.skip = function() { var prevTest = this.currentTest; this.createNextTest(); prevTest.timeout(); } MultiPointerTypeTest.prototype.done = function() { if (this.currentTest.status != 1) { var prevTest = this.currentTest; this.createNextTest(); if (prevTest != null) prevTest.done(); } } MultiPointerTypeTest.prototype.step = function(stepFunction) { this.currentTest.step(stepFunction); } MultiPointerTypeTest.prototype.createNextTest = function() { if (this.currentTypeIndex < this.types.length) { var pointerTypeDescription = document.getElementById('pointerTypeDescription'); document.getElementById('pointerTypeDescription').innerHTML = "Follow the test instructions with " + this.types[this.currentTypeIndex] + ". If you don't have the device skip it."; this.currentTest = async_test(this.types[this.currentTypeIndex] + ' ' + this.testName); expectedPointerType = this.types[this.currentTypeIndex]; this.currentTypeIndex++; } else { document.getElementById('pointerTypeDescription').innerHTML = ""; } resetTestState(); } function setup_pointerevent_test(testName, supportedPointerTypes) { return globalPointerEventTest = new MultiPointerTypeTest(testName, supportedPointerTypes); } function checkPointerEventType(event) { assert_equals(event.pointerType, expectedPointerType, "pointerType should be the same as the requested device."); } function touchScrollInTarget(target, direction) { var x_delta = 0; var y_delta = 0; if (direction == "down") { x_delta = 0; y_delta = -10; } else if (direction == "up") { x_delta = 0; y_delta = 10; } else if (direction == "right") { x_delta = -10; y_delta = 0; } else if (direction == "left") { x_delta = 10; y_delta = 0; } else { throw("scroll direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); } return new test_driver.Actions() .addPointer("touchPointer1", "touch") .pointerMove(0, 0, {origin: target}) .pointerDown() .pointerMove(x_delta, y_delta, {origin: target}) .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) .pointerMove(4 * x_delta, 4 * y_delta, {origin: target}) .pointerMove(5 * x_delta, 5 * y_delta, {origin: target}) .pointerMove(6 * x_delta, 6 * y_delta, {origin: target}) .pause(100) .pointerUp() .send(); } function clickInTarget(pointerType, target) { var pointerId = pointerType + "Pointer1"; return new test_driver.Actions() .addPointer(pointerId, pointerType) .pointerMove(0, 0, {origin: target}) .pointerDown() .pointerUp() .send(); } function rightClickInTarget(pointerType, target) { let pointerId = pointerType + "Pointer1"; let actions = new test_driver.Actions(); return actions.addPointer(pointerId, pointerType) .pointerMove(0, 0, {origin: target}) .pointerDown({button:actions.ButtonType.RIGHT}) .pointerUp({button:actions.ButtonType.RIGHT}) .send(); } function twoFingerDrag(target) { return new test_driver.Actions() .addPointer("touchPointer1", "touch") .addPointer("touchPointer2", "touch") .pointerMove(0, 0, {origin: target, sourceName: "touchPointer1"}) .pointerMove(10, 0, {origin: target, sourceName: "touchPointer2"}) .pointerDown({sourceName: "touchPointer1"}) .pointerDown({sourceName: "touchPointer2"}) .pointerMove(0, 10, {origin: target, sourceName: "touchPointer1"}) .pointerMove(10, 10, {origin: target, sourceName: "touchPointer2"}) .pointerMove(0, 20, {origin: target, sourceName: "touchPointer1"}) .pointerMove(10, 20, {origin: target, sourceName: "touchPointer2"}) .pause(100) .pointerUp({sourceName: "touchPointer1"}) .pointerUp({sourceName: "touchPointer2"}) .send(); } function pointerDragInTarget(pointerType, target, direction) { var x_delta = 0; var y_delta = 0; if (direction == "down") { x_delta = 0; y_delta = 10; } else if (direction == "up") { x_delta = 0; y_delta = -10; } else if (direction == "right") { x_delta = 10; y_delta = 0; } else if (direction == "left") { x_delta = -10; y_delta = 0; } else { throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); } var pointerId = pointerType + "Pointer1"; return new test_driver.Actions() .addPointer(pointerId, pointerType) .pointerMove(0, 0, {origin: target}) .pointerDown() .pointerMove(x_delta, y_delta, {origin: target}) .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) .pointerUp() .send(); } function pointerHoverInTarget(pointerType, target, direction) { var x_delta = 0; var y_delta = 0; if (direction == "down") { x_delta = 0; y_delta = 10; } else if (direction == "up") { x_delta = 0; y_delta = -10; } else if (direction == "right") { x_delta = 10; y_delta = 0; } else if (direction == "left") { x_delta = -10; y_delta = 0; } else { throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); } var pointerId = pointerType + "Pointer1"; return new test_driver.Actions() .addPointer(pointerId, pointerType) .pointerMove(0, 0, {origin: target}) .pointerMove(x_delta, y_delta, {origin: target}) .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) .send(); } function moveToDocument(pointerType) { var pointerId = pointerType + "Pointer1"; return new test_driver.Actions() .addPointer(pointerId, pointerType) .pointerMove(0, 0) .send(); } // Returns a promise that only gets resolved when the condition is met. function resolveWhen(condition) { return new Promise((resolve, reject) => { function tick() { if (condition()) resolve(); else requestAnimationFrame(tick.bind(this)); } tick(); }); } // Returns a promise that only gets resolved after n animation frames function waitForAnimationFrames(n){ let p = 0; function next(){ p++; return p === n; } return resolveWhen(next); } function isPointerEvent(eventName){ return All_Pointer_Events.includes(eventName); } function isMouseEvent(eventName){ return ["mousedown", "mouseup", "mousemove", "mouseover", "mouseenter", "mouseout", "mouseleave", "click", "contextmenu", "dblclick" ].includes(eventName); } function arePointerAndMouseEventCompatible(pointerEventName, mouseEventName){ // e.g. compatible pointer-mouse events: pointerup - mouseup etc return pointerEventName.startsWith("pointer") && mouseEventName.startsWith("mouse") && pointerEventName.substring(7) === mouseEventName.substring(5); } // events is a list of events fired at a target // checks to see if each pointer event has a corresponding mouse event in the event array // and the two events are in the proper order (pointer event is first) // see https://www.w3.org/TR/pointerevents3/#mapping-for-devices-that-support-hover function arePointerEventsBeforeCompatMouseEvents(events){ // checks to see if the pointer event is compatible with the mouse event // and the pointer event happens before the mouse event function arePointerAndMouseEventInProperOrder(pointerEventIndex, mouseEventIndex, events){ return (pointerEventIndex < mouseEventIndex && isPointerEvent(events[pointerEventIndex]) && isMouseEvent(events[mouseEventIndex]) && arePointerAndMouseEventCompatible(events[pointerEventIndex], events[mouseEventIndex])); } let currentPointerEventIndex = events.findIndex((event)=>isPointerEvent(event)); let currentMouseEventIndex = events.findIndex((event)=>isMouseEvent(event)); while(1){ if(currentMouseEventIndex < 0 && currentPointerEventIndex < 0) return true; if(currentMouseEventIndex < 0 || currentPointerEventIndex < 0) return false; if(!arePointerAndMouseEventInProperOrder(currentPointerEventIndex, currentMouseEventIndex, events)) return false; let pointerIdx = events.slice(currentPointerEventIndex+1).findIndex(isPointerEvent); let mouseIdx = events.slice(currentMouseEventIndex+1).findIndex(isMouseEvent); currentPointerEventIndex = (pointerIdx < 0)?pointerIdx:(currentPointerEventIndex+1+pointerIdx); currentMouseEventIndex = (mouseIdx < 0)?mouseIdx:(currentMouseEventIndex+1+mouseIdx); } return true; } // Returns a |Promise| that gets resolved with the event object when |target| // receives an event of type |event_type|. function getEvent(event_type, target) { return new Promise(resolve => { target.addEventListener(event_type, e => resolve(e), {once: true}); }); } // Returns a |Promise| that gets resolved with |event.data| when |window| // receives from |source| a "message" event whose |event.data.type| matches the string // |message_data_type|. function getMessageData(message_data_type, source) { return new Promise(resolve => { function waitAndRemove(e) { if (e.source != source || !e.data || e.data.type != message_data_type) return; window.removeEventListener("message", waitAndRemove); resolve(e.data); } window.addEventListener("message", waitAndRemove); }); }