pointerevent_support.js (17716B)
1 const All_Pointer_Events = [ 2 "pointerdown", 3 "pointerup", 4 "pointercancel", 5 "pointermove", 6 "pointerover", 7 "pointerout", 8 "pointerenter", 9 "pointerleave", 10 "gotpointercapture", 11 "lostpointercapture" 12 ]; 13 14 // https://w3c.github.io/pointerevents/#the-button-property 15 // Values for the button property, which indicates the device button whose state 16 // change fired the event. 17 const ButtonChange = { 18 NONE: -1, 19 PEN_CONTACT: 0, 20 TOUCH_CONTACT: 0, 21 LEFT_MOUSE: 0, 22 MIDDLE_MOUSE: 1, 23 RIGHT_MOUSE: 2, 24 X1_MOUSE: 3, 25 X2_MOUSE: 4, 26 PEN_ERASER_BUTTON: 5 27 }; 28 29 // https://w3c.github.io/pointerevents/#the-buttons-property 30 // The buttons property gives the current state of the device buttons as a 31 // bitmask. 32 const ButtonsBitfield = { 33 NONE: 0, 34 PEN_CONTACT: 1, 35 TOUCH_CONTACT: 1, 36 LEFT_MOUSE: 1, 37 RIGHT_MOUSE: 2, 38 PEN_BARREL_BUTTON: 2, 39 MIDDLE_MOUSE: 4, 40 X1_MOUSE: 8, 41 X2_MOUSE: 16, 42 PEN_ERASER_BUTTON: 32 43 }; 44 45 // Check for conformance to PointerEvent interface 46 // https://w3c.github.io/pointerevents/#pointerevent-interface 47 function check_PointerEvent(event, testNamePrefix, standardAttrs = true) { 48 if (testNamePrefix === undefined) 49 testNamePrefix = ""; 50 51 // Use expectedPointerType if set otherwise just use the incoming event pointerType in the test name. 52 var pointerTestName = (testNamePrefix ? testNamePrefix + ' ' : '') 53 + (expectedPointerType == null ? event.pointerType : expectedPointerType) + ' ' + event.type; 54 55 if (standardAttrs) { 56 if (expectedPointerType != null) { 57 test(function () { 58 assert_equals(event.pointerType, expectedPointerType); 59 }, pointerTestName + ".pointerType is correct."); 60 } 61 62 test(function () { 63 assert_true(event instanceof event.target.ownerDocument.defaultView.PointerEvent); 64 }, pointerTestName + " event is a PointerEvent event"); 65 } 66 67 // Check attributes for conformance to WebIDL (existence, type, being readable). 68 var idl_type_check = { 69 "long": function (v) { return typeof v === "number" && Math.round(v) === v; }, 70 "float": function (v) { return typeof v === "number"; }, 71 "string": function (v) { return typeof v === "string"; }, 72 "boolean": function (v) { return typeof v === "boolean" }, 73 "object": function (v) { return typeof v === "object" } 74 }; 75 76 // Check values for inherited attributes. 77 // https://w3c.github.io/pointerevents/#attributes-and-default-actions 78 if (!standardAttrs) { 79 test(function () { 80 assert_implements_optional("fromElement" in event); 81 assert_equals(event.fromElement, null); 82 }, pointerTestName + ".fromElement value is null"); 83 test(function () { 84 assert_implements_optional("toElement" in event); 85 assert_equals(event.toElement, null); 86 }, pointerTestName + ".toElement value is null"); 87 } else { 88 test(function () { 89 assert_equals(event.isTrusted, true); 90 }, pointerTestName + ".isTrusted value is true"); 91 test(function () { 92 let expected = (event.type != 'pointerenter' && event.type != 'pointerleave'); 93 assert_equals(event.composed, expected); 94 }, pointerTestName + ".composed value is valid"); 95 test(function () { 96 let expected = (event.type != 'pointerenter' && event.type != 'pointerleave'); 97 assert_equals(event.bubbles, expected); 98 }, pointerTestName + ".bubbles value is valid"); 99 test(function () { 100 let cancelable_events = [ 101 'pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout' 102 ]; 103 assert_equals(event.cancelable, cancelable_events.includes(event.type)); 104 }, pointerTestName + ".cancelable value is valid"); 105 106 // Check the pressure value. 107 // https://w3c.github.io/pointerevents/#dom-pointerevent-pressure 108 test(function () { 109 assert_greater_than_equal(event.pressure, 0, "pressure is greater than or equal to 0"); 110 assert_less_than_equal(event.pressure, 1, "pressure is less than or equal to 1"); 111 112 if (event.buttons === 0) { 113 assert_equals(event.pressure, 0, "pressure is 0 with no buttons pressed"); 114 } else { 115 assert_greater_than(event.pressure, 0, "pressure is greater than 0 with a button pressed"); 116 if (event.pointerType === "mouse") { 117 assert_equals(event.pressure, 0.5, "pressure is 0.5 for mouse with a button pressed"); 118 } 119 } 120 }, pointerTestName + ".pressure value is valid"); 121 122 // Check mouse-specific properties. 123 if (event.pointerType === "mouse") { 124 test(function () { 125 assert_equals(event.width, 1, "width of mouse should be 1"); 126 assert_equals(event.height, 1, "height of mouse should be 1"); 127 assert_equals(event.tiltX, 0, event.type + ".tiltX is 0 for mouse"); 128 assert_equals(event.tiltY, 0, event.type + ".tiltY is 0 for mouse"); 129 assert_true(event.isPrimary, event.type + ".isPrimary is true for mouse"); 130 }, pointerTestName + " properties for pointerType = mouse"); 131 } 132 133 // Check "pointerup" specific properties. 134 if (event.type == "pointerup") { 135 test(function () { 136 assert_equals(event.width, 1, "width of pointerup should be 1"); 137 assert_equals(event.height, 1, "height of pointerup should be 1"); 138 }, pointerTestName + " properties for pointerup"); 139 } 140 } 141 } 142 143 function showPointerTypes() { 144 var complete_notice = document.getElementById("complete-notice"); 145 var pointertype_log = document.getElementById("pointertype-log"); 146 var pointertypes = Object.keys(detected_pointertypes); 147 pointertype_log.innerHTML = pointertypes.length ? 148 pointertypes.join(",") : "(none)"; 149 complete_notice.style.display = "block"; 150 } 151 152 function showLoggedEvents() { 153 var event_log_elem = document.getElementById("event-log"); 154 event_log_elem.innerHTML = event_log.length ? event_log.join(", ") : "(none)"; 155 156 var complete_notice = document.getElementById("complete-notice"); 157 complete_notice.style.display = "block"; 158 } 159 160 function failOnScroll() { 161 assert_true(false, 162 "scroll received while shouldn't"); 163 } 164 165 function updateDescriptionNextStep() { 166 document.getElementById('desc').innerHTML = "Test Description: Try to scroll text RIGHT."; 167 } 168 169 function updateDescriptionComplete() { 170 document.getElementById('desc').innerHTML = "Test Description: Test complete"; 171 } 172 173 function objectScroller(target, direction, value) { 174 if (direction == 'up') { 175 target.scrollTop = 0; 176 } else if (direction == 'left') { 177 target.scrollLeft = 0; 178 } 179 } 180 181 function sPointerCapture(e) { 182 try { 183 target0.setPointerCapture(e.pointerId); 184 } 185 catch(e) { 186 } 187 } 188 189 function rPointerCapture(e) { 190 try { 191 captureButton.value = 'Set Capture'; 192 target0.releasePointerCapture(e.pointerId); 193 } 194 catch(e) { 195 } 196 } 197 198 var globalPointerEventTest = null; 199 var expectedPointerType = null; 200 const ALL_POINTERS = ['mouse', 'touch', 'pen']; 201 202 function MultiPointerTypeTest(testName, types) { 203 this.testName = testName; 204 this.types = types; 205 this.currentTypeIndex = 0; 206 this.currentTest = null; 207 this.createNextTest(); 208 } 209 210 MultiPointerTypeTest.prototype.step = function(op) { 211 this.currentTest.step(op); 212 } 213 214 MultiPointerTypeTest.prototype.skip = function() { 215 var prevTest = this.currentTest; 216 this.createNextTest(); 217 prevTest.timeout(); 218 } 219 220 MultiPointerTypeTest.prototype.done = function() { 221 if (this.currentTest.status != 1) { 222 var prevTest = this.currentTest; 223 this.createNextTest(); 224 if (prevTest != null) 225 prevTest.done(); 226 } 227 } 228 229 MultiPointerTypeTest.prototype.step = function(stepFunction) { 230 this.currentTest.step(stepFunction); 231 } 232 233 MultiPointerTypeTest.prototype.createNextTest = function() { 234 if (this.currentTypeIndex < this.types.length) { 235 var pointerTypeDescription = document.getElementById('pointerTypeDescription'); 236 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>."; 237 this.currentTest = async_test(this.types[this.currentTypeIndex] + ' ' + this.testName); 238 expectedPointerType = this.types[this.currentTypeIndex]; 239 this.currentTypeIndex++; 240 } else { 241 document.getElementById('pointerTypeDescription').innerHTML = ""; 242 } 243 resetTestState(); 244 } 245 246 function setup_pointerevent_test(testName, supportedPointerTypes) { 247 return globalPointerEventTest = new MultiPointerTypeTest(testName, supportedPointerTypes); 248 } 249 250 function checkPointerEventType(event) { 251 assert_equals(event.pointerType, expectedPointerType, "pointerType should be the same as the requested device."); 252 } 253 254 function touchScrollInTarget(target, direction) { 255 var x_delta = 0; 256 var y_delta = 0; 257 if (direction == "down") { 258 x_delta = 0; 259 y_delta = -10; 260 } else if (direction == "up") { 261 x_delta = 0; 262 y_delta = 10; 263 } else if (direction == "right") { 264 x_delta = -10; 265 y_delta = 0; 266 } else if (direction == "left") { 267 x_delta = 10; 268 y_delta = 0; 269 } else { 270 throw("scroll direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); 271 } 272 return new test_driver.Actions() 273 .addPointer("touchPointer1", "touch") 274 .pointerMove(0, 0, {origin: target}) 275 .pointerDown() 276 .pointerMove(x_delta, y_delta, {origin: target}) 277 .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) 278 .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) 279 .pointerMove(4 * x_delta, 4 * y_delta, {origin: target}) 280 .pointerMove(5 * x_delta, 5 * y_delta, {origin: target}) 281 .pointerMove(6 * x_delta, 6 * y_delta, {origin: target}) 282 .pause(100) 283 .pointerUp() 284 .send(); 285 } 286 287 function clickInTarget(pointerType, target) { 288 var pointerId = pointerType + "Pointer1"; 289 return new test_driver.Actions() 290 .addPointer(pointerId, pointerType) 291 .pointerMove(0, 0, {origin: target}) 292 .pointerDown() 293 .pointerUp() 294 .send(); 295 } 296 297 function rightClickInTarget(pointerType, target) { 298 let pointerId = pointerType + "Pointer1"; 299 let actions = new test_driver.Actions(); 300 return actions.addPointer(pointerId, pointerType) 301 .pointerMove(0, 0, {origin: target}) 302 .pointerDown({button:actions.ButtonType.RIGHT}) 303 .pointerUp({button:actions.ButtonType.RIGHT}) 304 .send(); 305 } 306 307 function twoFingerDrag(target) { 308 return new test_driver.Actions() 309 .addPointer("touchPointer1", "touch") 310 .addPointer("touchPointer2", "touch") 311 .pointerMove(0, 0, { origin: target, sourceName: "touchPointer1" }) 312 .pointerMove(10, 0, { origin: target, sourceName: "touchPointer2" }) 313 .pointerDown({ sourceName: "touchPointer1" }) 314 .pointerDown({ sourceName: "touchPointer2" }) 315 .pointerMove(0, 10, { origin: target, sourceName: "touchPointer1" }) 316 .pointerMove(10, 10, { origin: target, sourceName: "touchPointer2" }) 317 .pointerMove(0, 20, { origin: target, sourceName: "touchPointer1" }) 318 .pointerMove(10, 20, { origin: target, sourceName: "touchPointer2" }) 319 .pause(100) 320 .pointerUp({ sourceName: "touchPointer1" }) 321 .pointerUp({ sourceName: "touchPointer2" }) 322 .send(); 323 } 324 325 function pointerDragInTarget(pointerType, target, direction) { 326 var x_delta = 0; 327 var y_delta = 0; 328 if (direction == "down") { 329 x_delta = 0; 330 y_delta = 10; 331 } else if (direction == "up") { 332 x_delta = 0; 333 y_delta = -10; 334 } else if (direction == "right") { 335 x_delta = 10; 336 y_delta = 0; 337 } else if (direction == "left") { 338 x_delta = -10; 339 y_delta = 0; 340 } else { 341 throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); 342 } 343 var pointerId = pointerType + "Pointer1"; 344 return new test_driver.Actions() 345 .addPointer(pointerId, pointerType) 346 .pointerMove(0, 0, {origin: target}) 347 .pointerDown() 348 .pointerMove(x_delta, y_delta, {origin: target}) 349 .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) 350 .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) 351 .pointerUp() 352 .send(); 353 } 354 355 function pointerHoverInTarget(pointerType, target, direction) { 356 var x_delta = 0; 357 var y_delta = 0; 358 if (direction == "down") { 359 x_delta = 0; 360 y_delta = 10; 361 } else if (direction == "up") { 362 x_delta = 0; 363 y_delta = -10; 364 } else if (direction == "right") { 365 x_delta = 10; 366 y_delta = 0; 367 } else if (direction == "left") { 368 x_delta = -10; 369 y_delta = 0; 370 } else { 371 throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); 372 } 373 var pointerId = pointerType + "Pointer1"; 374 return new test_driver.Actions() 375 .addPointer(pointerId, pointerType) 376 .pointerMove(0, 0, {origin: target}) 377 .pointerMove(x_delta, y_delta, {origin: target}) 378 .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) 379 .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) 380 .send(); 381 } 382 383 function moveToDocument(pointerType) { 384 var pointerId = pointerType + "Pointer1"; 385 return new test_driver.Actions() 386 .addPointer(pointerId, pointerType) 387 // WebDriver initializes the pointer position (0, 0), therefore, we need 388 // to move different position first. Otherwise, moving to (0, 0) may be 389 // ignored. 390 .pointerMove(1, 1) 391 .pointerMove(0, 0) 392 .send(); 393 } 394 395 // Returns a promise that only gets resolved when the condition is met. 396 function resolveWhen(condition) { 397 return new Promise((resolve, reject) => { 398 function tick() { 399 if (condition()) 400 resolve(); 401 else 402 requestAnimationFrame(tick.bind(this)); 403 } 404 tick(); 405 }); 406 } 407 408 // Returns a promise that only gets resolved after n animation frames 409 function waitForAnimationFrames(n) { 410 let p = 0; 411 function next() { 412 p++; 413 return p === n; 414 } 415 return resolveWhen(next); 416 } 417 418 function isPointerEvent(eventName) { 419 return All_Pointer_Events.includes(eventName); 420 } 421 422 function isMouseEvent(eventName) { 423 return ["mousedown", "mouseup", "mousemove", "mouseover", 424 "mouseenter", "mouseout", "mouseleave", 425 "click", "contextmenu", "dblclick" 426 ].includes(eventName); 427 } 428 429 // Events is a list of events fired at a target. 430 // 431 // Checks to see if each pointer event has a corresponding mouse event in the 432 // event array and the two events are in the proper order (pointer event is 433 // first). 434 // 435 // See https://w3c.github.io/pointerevents/#mapping-for-devices-that-support-hover 436 function arePointerEventsBeforeCompatMouseEvents(events) { 437 function arePointerAndMouseEventCompatible(pointerEventName, mouseEventName) { 438 return pointerEventName.startsWith("pointer") 439 && mouseEventName.startsWith("mouse") 440 && pointerEventName.substring(7) === mouseEventName.substring(5); 441 } 442 443 function arePointerAndMouseEventInProperOrder(pointerEventIndex, mouseEventIndex, events) { 444 return (pointerEventIndex < mouseEventIndex && isPointerEvent(events[pointerEventIndex]) && isMouseEvent(events[mouseEventIndex]) 445 && arePointerAndMouseEventCompatible(events[pointerEventIndex], events[mouseEventIndex])); 446 } 447 448 let currentPointerEventIndex = events.findIndex((event) => isPointerEvent(event)); 449 let currentMouseEventIndex = events.findIndex((event) => isMouseEvent(event)); 450 451 while (1) { 452 if (currentMouseEventIndex < 0 && currentPointerEventIndex < 0) 453 return true; 454 if (currentMouseEventIndex < 0 || currentPointerEventIndex < 0) 455 return false; 456 if (!arePointerAndMouseEventInProperOrder(currentPointerEventIndex, currentMouseEventIndex, events)) 457 return false; 458 459 let pointerIdx = events.slice(currentPointerEventIndex + 1).findIndex(isPointerEvent); 460 let mouseIdx = events.slice(currentMouseEventIndex + 1).findIndex(isMouseEvent); 461 462 currentPointerEventIndex = (pointerIdx < 0) ? pointerIdx : (currentPointerEventIndex + 1 + pointerIdx); 463 currentMouseEventIndex = (mouseIdx < 0) ? mouseIdx : (currentMouseEventIndex + 1 + mouseIdx); 464 } 465 466 return true; 467 } 468 469 // Returns a |Promise| that gets resolved with the event object when |target| 470 // receives an event of type |event_type|. 471 // 472 // The optional |test| parameter adds event handler cleanup for the case |test| 473 // terminates before the event is received. 474 function getEvent(event_type, target, test) { 475 return new Promise(resolve => { 476 const listener = e => resolve(e); 477 target.addEventListener(event_type, listener, { once: true }); 478 if (test) { 479 test.add_cleanup(() => 480 target.removeEventListener(event_type, listener, { once: true })); 481 } 482 }); 483 } 484 485 // Returns a |Promise| that gets resolved with |event.data| when |window| 486 // receives from |source| a "message" event whose |event.data.type| matches the 487 // string |message_data_type|. 488 // 489 // The optional |test| parameter adds event handler cleanup for the case |test| 490 // terminates before a matching event is received. 491 function getMessageData(message_data_type, source, test) { 492 return new Promise(resolve => { 493 const listener = e => { 494 if (e.source != source || !e.data || e.data.type != message_data_type) 495 return; 496 window.removeEventListener("message", listener); 497 resolve(e.data); 498 } 499 500 window.addEventListener("message", listener); 501 if (test) { 502 test.add_cleanup(() => 503 window.removeEventListener("message", listener)); 504 } 505 }); 506 } 507 508 // The optional |test| parameter adds event handler cleanup for the case |test| 509 // terminates before the event is received. 510 function preventDefaultPointerdownOnce(target, test) { 511 return new Promise((resolve) => { 512 const listener = e => { 513 e.preventDefault(); 514 resolve(); 515 } 516 517 target.addEventListener("pointerdown", listener, { once: true }); 518 if (test) { 519 test.add_cleanup(() => 520 target.removeEventListener("pointerdown", listener, { once: true })); 521 } 522 }); 523 }