LibWeb: Add definitions for PointerEvent event handlers

Also removing a FIXME about not covering all of the event names as it is
not exactly clear when such a FIXME would be addressed, especially as
these come from multiple specifications.
This commit is contained in:
Shannon Booth 2024-11-22 23:58:16 +13:00 committed by Alexander Kalenik
parent 4d38e04e48
commit 75b7a3e413
Notes: github-actions[bot] 2024-11-22 13:34:51 +00:00
8 changed files with 806 additions and 27 deletions

View file

@ -86,6 +86,19 @@ interface mixin GlobalEventHandlers {
attribute EventHandler onwebkitanimationstart;
attribute EventHandler onwebkittransitionend;
attribute EventHandler onwheel;
// https://w3c.github.io/pointerevents/#extensions-to-the-globaleventhandlers-mixin
attribute EventHandler onpointerover;
attribute EventHandler onpointerenter;
attribute EventHandler onpointerdown;
attribute EventHandler onpointermove;
[SecureContext] attribute EventHandler onpointerrawupdate;
attribute EventHandler onpointerup;
attribute EventHandler onpointercancel;
attribute EventHandler onpointerout;
attribute EventHandler onpointerleave;
attribute EventHandler ongotpointercapture;
attribute EventHandler onlostpointercapture;
};
// https://html.spec.whatwg.org/#windoweventhandlers

View file

@ -159,6 +159,7 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(onfocusin) \
__ENUMERATE_HTML_ATTRIBUTE(onfocusout) \
__ENUMERATE_HTML_ATTRIBUTE(onformdata) \
__ENUMERATE_HTML_ATTRIBUTE(ongotpointercapture) \
__ENUMERATE_HTML_ATTRIBUTE(onhashchange) \
__ENUMERATE_HTML_ATTRIBUTE(oninput) \
__ENUMERATE_HTML_ATTRIBUTE(oninvalid) \
@ -170,6 +171,7 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(onloadeddata) \
__ENUMERATE_HTML_ATTRIBUTE(onloadedmetadata) \
__ENUMERATE_HTML_ATTRIBUTE(onloadstart) \
__ENUMERATE_HTML_ATTRIBUTE(onlostpointercapture) \
__ENUMERATE_HTML_ATTRIBUTE(onmessage) \
__ENUMERATE_HTML_ATTRIBUTE(onmessageerror) \
__ENUMERATE_HTML_ATTRIBUTE(onmousedown) \
@ -186,6 +188,15 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(onpause) \
__ENUMERATE_HTML_ATTRIBUTE(onplay) \
__ENUMERATE_HTML_ATTRIBUTE(onplaying) \
__ENUMERATE_HTML_ATTRIBUTE(onpointercancel) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerdown) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerenter) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerleave) \
__ENUMERATE_HTML_ATTRIBUTE(onpointermove) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerout) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerover) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerrawupdate) \
__ENUMERATE_HTML_ATTRIBUTE(onpointerup) \
__ENUMERATE_HTML_ATTRIBUTE(onpopstate) \
__ENUMERATE_HTML_ATTRIBUTE(onprogress) \
__ENUMERATE_HTML_ATTRIBUTE(onratechange) \

View file

@ -37,6 +37,7 @@
E(onfocusin, HTML::EventNames::focusin) \
E(onfocusout, HTML::EventNames::focusout) \
E(onformdata, HTML::EventNames::formdata) \
E(ongotpointercapture, UIEvents::EventNames::gotpointercapture) \
E(oninput, HTML::EventNames::input) \
E(oninvalid, HTML::EventNames::invalid) \
E(onkeydown, UIEvents::EventNames::keydown) \
@ -46,6 +47,7 @@
E(onloadeddata, HTML::EventNames::loadeddata) \
E(onloadedmetadata, HTML::EventNames::loadedmetadata) \
E(onloadstart, HTML::EventNames::loadstart) \
E(onlostpointercapture, UIEvents::EventNames::lostpointercapture) \
E(onmousedown, UIEvents::EventNames::mousedown) \
E(onmouseenter, UIEvents::EventNames::mouseenter) \
E(onmouseleave, UIEvents::EventNames::mouseleave) \
@ -56,6 +58,15 @@
E(onpause, HTML::EventNames::pause) \
E(onplay, HTML::EventNames::play) \
E(onplaying, HTML::EventNames::playing) \
E(onpointercancel, UIEvents::EventNames::pointercancel) \
E(onpointerdown, UIEvents::EventNames::pointerdown) \
E(onpointerenter, UIEvents::EventNames::pointerenter) \
E(onpointerleave, UIEvents::EventNames::pointerleave) \
E(onpointermove, UIEvents::EventNames::pointermove) \
E(onpointerout, UIEvents::EventNames::pointerout) \
E(onpointerover, UIEvents::EventNames::pointerover) \
E(onpointerrawupdate, UIEvents::EventNames::pointerrawupdate) \
E(onpointerup, UIEvents::EventNames::pointerup) \
E(onprogress, HTML::EventNames::progress) \
E(onratechange, HTML::EventNames::ratechange) \
E(onreset, HTML::EventNames::reset) \

View file

@ -12,33 +12,35 @@
namespace Web::UIEvents::EventNames {
// FIXME: This is not all of the events
#define ENUMERATE_UI_EVENTS \
__ENUMERATE_UI_EVENT(auxclick) \
__ENUMERATE_UI_EVENT(beforeinput) \
__ENUMERATE_UI_EVENT(click) \
__ENUMERATE_UI_EVENT(contextmenu) \
__ENUMERATE_UI_EVENT(dblclick) \
__ENUMERATE_UI_EVENT(input) \
__ENUMERATE_UI_EVENT(keydown) \
__ENUMERATE_UI_EVENT(keypress) \
__ENUMERATE_UI_EVENT(keyup) \
__ENUMERATE_UI_EVENT(mousedown) \
__ENUMERATE_UI_EVENT(mouseenter) \
__ENUMERATE_UI_EVENT(mouseleave) \
__ENUMERATE_UI_EVENT(mousemove) \
__ENUMERATE_UI_EVENT(mouseout) \
__ENUMERATE_UI_EVENT(mouseover) \
__ENUMERATE_UI_EVENT(mouseup) \
__ENUMERATE_UI_EVENT(pointerdown) \
__ENUMERATE_UI_EVENT(pointerenter) \
__ENUMERATE_UI_EVENT(pointerleave) \
__ENUMERATE_UI_EVENT(pointermove) \
__ENUMERATE_UI_EVENT(pointerout) \
__ENUMERATE_UI_EVENT(pointerover) \
__ENUMERATE_UI_EVENT(pointerup) \
__ENUMERATE_UI_EVENT(resize) \
#define ENUMERATE_UI_EVENTS \
__ENUMERATE_UI_EVENT(auxclick) \
__ENUMERATE_UI_EVENT(beforeinput) \
__ENUMERATE_UI_EVENT(click) \
__ENUMERATE_UI_EVENT(contextmenu) \
__ENUMERATE_UI_EVENT(dblclick) \
__ENUMERATE_UI_EVENT(gotpointercapture) \
__ENUMERATE_UI_EVENT(input) \
__ENUMERATE_UI_EVENT(keydown) \
__ENUMERATE_UI_EVENT(keypress) \
__ENUMERATE_UI_EVENT(keyup) \
__ENUMERATE_UI_EVENT(lostpointercapture) \
__ENUMERATE_UI_EVENT(mousedown) \
__ENUMERATE_UI_EVENT(mouseenter) \
__ENUMERATE_UI_EVENT(mouseleave) \
__ENUMERATE_UI_EVENT(mousemove) \
__ENUMERATE_UI_EVENT(mouseout) \
__ENUMERATE_UI_EVENT(mouseover) \
__ENUMERATE_UI_EVENT(mouseup) \
__ENUMERATE_UI_EVENT(pointercancel) \
__ENUMERATE_UI_EVENT(pointerdown) \
__ENUMERATE_UI_EVENT(pointerenter) \
__ENUMERATE_UI_EVENT(pointerleave) \
__ENUMERATE_UI_EVENT(pointermove) \
__ENUMERATE_UI_EVENT(pointerout) \
__ENUMERATE_UI_EVENT(pointerover) \
__ENUMERATE_UI_EVENT(pointerrawupdate) \
__ENUMERATE_UI_EVENT(pointerup) \
__ENUMERATE_UI_EVENT(resize) \
__ENUMERATE_UI_EVENT(wheel)
#define __ENUMERATE_UI_EVENT(name) extern FlyString name;

View file

@ -0,0 +1,40 @@
Summary
Harness status: OK
Rerun
Found 30 tests
30 Pass
Details
Result Test Name MessagePass The default value of onpointerdown is always null
Pass The onpointerdown content attribute must be compiled into the onpointerdown property
Pass dispatching a pointerdown event must trigger element.onpointerdown
Pass The default value of onpointerup is always null
Pass The onpointerup content attribute must be compiled into the onpointerup property
Pass dispatching a pointerup event must trigger element.onpointerup
Pass The default value of onpointercancel is always null
Pass The onpointercancel content attribute must be compiled into the onpointercancel property
Pass dispatching a pointercancel event must trigger element.onpointercancel
Pass The default value of onpointermove is always null
Pass The onpointermove content attribute must be compiled into the onpointermove property
Pass dispatching a pointermove event must trigger element.onpointermove
Pass The default value of onpointerover is always null
Pass The onpointerover content attribute must be compiled into the onpointerover property
Pass dispatching a pointerover event must trigger element.onpointerover
Pass The default value of onpointerout is always null
Pass The onpointerout content attribute must be compiled into the onpointerout property
Pass dispatching a pointerout event must trigger element.onpointerout
Pass The default value of onpointerenter is always null
Pass The onpointerenter content attribute must be compiled into the onpointerenter property
Pass dispatching a pointerenter event must trigger element.onpointerenter
Pass The default value of onpointerleave is always null
Pass The onpointerleave content attribute must be compiled into the onpointerleave property
Pass dispatching a pointerleave event must trigger element.onpointerleave
Pass The default value of ongotpointercapture is always null
Pass The ongotpointercapture content attribute must be compiled into the ongotpointercapture property
Pass dispatching a gotpointercapture event must trigger element.ongotpointercapture
Pass The default value of onlostpointercapture is always null
Pass The onlostpointercapture content attribute must be compiled into the onlostpointercapture property
Pass dispatching a lostpointercapture event must trigger element.onlostpointercapture

View file

@ -0,0 +1,67 @@
<!doctype html>
<html>
<head>
<title>PointerEvent: Constructor test</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<!-- Additional helper script for common checks across event types -->
<script type="text/javascript" src="pointerevent_support.js"></script>
</head>
<body>
<h1>PointerEvent: Dispatch custom event</h1>
<h4>Test Description: This test checks if on pointer event handlers through attributes works properly using synthetic pointerevents. For valid results, this test must be run without generating real (trusted) events on the black rectangle below.</h4>
<div id="target0"
onpointercancel="window.eventHappened = 'pointercancel';"
onpointerdown="window.eventHappened = 'pointerdown';"
onpointerup="window.eventHappened = 'pointerup';"
onpointermove="window.eventHappened = 'pointermove';"
onpointerout="window.eventHappened = 'pointerout';"
onpointerover="window.eventHappened = 'pointerover';"
onpointerleave="window.eventHappened = 'pointerleave';"
onpointerenter="window.eventHappened = 'pointerenter';"
ongotpointercapture="window.eventHappened = 'gotpointercapture';"
onlostpointercapture="window.eventHappened = 'lostpointercapture';"
></div>
<script>
window.eventHappened = '';
All_Pointer_Events.forEach(function(event) {
var on_event = "on" + event;
test(function() {
const htmlElement = document.createElement("span");
const svgElement = document.createElementNS("http://www.w3.org/2000/svg", "g");
for (var location of [window, htmlElement, svgElement, document]) {
assert_equals(location[on_event], null,
`The default value of the property is null for a ${location.constructor.name} instance`);
}
}, "The default value of " + on_event + " is always null");
test(function() {
window.eventHappened = '';
const element = document.querySelector("#target0");
const compiledHandler = element[on_event];
assert_equals(typeof compiledHandler, "function", "The " + on_event + " property must be a function");
compiledHandler();
assert_equals(window.eventHappened, event, "Calling the handler must run the code");
}, "The " + on_event + " content attribute must be compiled into the " + on_event + " property");
var handlerTest = async_test("dispatching a " + event + " event must trigger element." + on_event);
const element = document.createElement("meta");
element[on_event] = function(e) {
handlerTest.step(function() {
assert_equals(e.currentTarget, element, "The event must be fired at the <meta> element");
});
handlerTest.done();
};
element.dispatchEvent(new Event(event));
});
</script>
<div id="complete-notice">
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View file

@ -0,0 +1,112 @@
#innerFrame {
position: absolute;
top: 300px;
left: 200px;
height: 100px;
width: 100px;
}
.spacer {
height: 100px;
}
#square1 {
top: 330px;
left: 150px;
background: black;
}
#square2 {
top: 50px;
left: 30px;
visibility: hidden;
background: red;
}
.square {
height: 20px;
width: 20px;
position: absolute;
padding: 0px;
}
#target0 {
background: black;
color: white;
white-space: nowrap;
overflow-y: auto;
overflow-x: auto;
}
#target1 {
background: purple;
color: white;
white-space: nowrap;
overflow-y: auto;
overflow-x: auto;
}
#scrollTarget {
background: darkblue;
}
.touchActionNone {
touch-action: none;
}
#innerframe {
width: 90%;
margin: 10px;
margin-left: 10%;
height: 200px;
}
.scroller {
width: 700px;
height: 430px;
margin: 20px;
overflow: auto;
background: black;
}
.scroller > div {
height: 1000px;
width: 1000px;
color: white;
}
.scroller > div div {
height: 100%;
width: 100%;
color: white;
}
div {
margin: 0em;
padding: 1.2em;
}
#complete-notice {
background: #afa;
border: 1px solid #0a0;
display: none;
}
#pointertype-log {
font-weight: bold;
}
#event-log {
font-weight: bold;
}
#listener {
background: orange;
border: 1px solid orange;
position: absolute;
top: -100px;
}
body.scrollable {
min-height: 5000px;
}

View file

@ -0,0 +1,523 @@
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, standardAttrs = true) {
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 (standardAttrs) {
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" }
};
// Check values for inherited attributes.
// https://w3c.github.io/pointerevents/#attributes-and-default-actions
if (!standardAttrs) {
test(function () {
assert_implements_optional("fromElement" in event);
assert_equals(event.fromElement, null);
}, pointerTestName + ".fromElement value is null");
test(function () {
assert_implements_optional("toElement" in event);
assert_equals(event.toElement, null);
}, pointerTestName + ".toElement value is null");
} else {
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");
test(function () {
let cancelable_events = [
'pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'
];
assert_equals(event.cancelable, cancelable_events.includes(event.type));
}, pointerTestName + ".cancelable 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 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)
// WebDriver initializes the pointer position (0, 0), therefore, we need
// to move different position first. Otherwise, moving to (0, 0) may be
// ignored.
.pointerMove(1, 1)
.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);
}
// 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://w3c.github.io/pointerevents/#mapping-for-devices-that-support-hover
function arePointerEventsBeforeCompatMouseEvents(events) {
function arePointerAndMouseEventCompatible(pointerEventName, mouseEventName) {
return pointerEventName.startsWith("pointer")
&& mouseEventName.startsWith("mouse")
&& pointerEventName.substring(7) === mouseEventName.substring(5);
}
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|.
//
// The optional |test| parameter adds event handler cleanup for the case |test|
// terminates before the event is received.
function getEvent(event_type, target, test) {
return new Promise(resolve => {
const listener = e => resolve(e);
target.addEventListener(event_type, listener, { once: true });
if (test) {
test.add_cleanup(() =>
target.removeEventListener(event_type, listener, { 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|.
//
// The optional |test| parameter adds event handler cleanup for the case |test|
// terminates before a matching event is received.
function getMessageData(message_data_type, source, test) {
return new Promise(resolve => {
const listener = e => {
if (e.source != source || !e.data || e.data.type != message_data_type)
return;
window.removeEventListener("message", listener);
resolve(e.data);
}
window.addEventListener("message", listener);
if (test) {
test.add_cleanup(() =>
window.removeEventListener("message", listener));
}
});
}
// The optional |test| parameter adds event handler cleanup for the case |test|
// terminates before the event is received.
function preventDefaultPointerdownOnce(target, test) {
return new Promise((resolve) => {
const listener = e => {
e.preventDefault();
resolve();
}
target.addEventListener("pointerdown", listener, { once: true });
if (test) {
test.add_cleanup(() =>
target.removeEventListener("pointerdown", listener, { once: true }));
}
});
}