diff options
Diffstat (limited to 'testing/web-platform/tests/pointerevents/pointerevent_support.js')
-rw-r--r-- | testing/web-platform/tests/pointerevents/pointerevent_support.js | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/testing/web-platform/tests/pointerevents/pointerevent_support.js b/testing/web-platform/tests/pointerevents/pointerevent_support.js new file mode 100644 index 0000000000..a4b7adce3e --- /dev/null +++ b/testing/web-platform/tests/pointerevents/pointerevent_support.js @@ -0,0 +1,525 @@ +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 <span style='color: red'>" + this.types[this.currentTypeIndex] + "</span>. If you don't have the device <a href='javascript:;' onclick='globalPointerEventTest.skip()'>skip it</a>."; + 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); + }); +} |