301 lines
11 KiB
HTML
301 lines
11 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<title>Pointer Events properties tests</title>
|
|
<meta name="viewport" content="width=device-width">
|
|
<meta name="variant" content="?mouse">
|
|
<meta name="variant" content="?pen">
|
|
<meta name="variant" content="?mouse-right">
|
|
<meta name="variant" content="?pen-right">
|
|
<meta name="variant" content="?touch">
|
|
<meta name="variant" content="?mouse-nonstandard">
|
|
<meta name="variant" content="?pen-nonstandard">
|
|
<meta name="variant" content="?mouse-right-nonstandard">
|
|
<meta name="variant" content="?pen-right-nonstandard">
|
|
<meta name="variant" content="?touch-nonstandard">
|
|
<link rel="stylesheet" type="text/css" href="pointerevent_styles.css">
|
|
<style>
|
|
html {
|
|
touch-action: none;
|
|
}
|
|
|
|
div {
|
|
padding: 0;
|
|
}
|
|
|
|
#square1 {
|
|
background-color: green;
|
|
border: 1px solid black;
|
|
height: 50px;
|
|
width: 50px;
|
|
margin-bottom: 3px;
|
|
display: inline-block;
|
|
}
|
|
|
|
#innerFrame {
|
|
position: relative;
|
|
margin-bottom: 3px;
|
|
margin-left: 0;
|
|
top: 0;
|
|
left: 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/resources/testdriver.js"></script>
|
|
<script src="/resources/testdriver-actions.js"></script>
|
|
<script src="/resources/testdriver-vendor.js"></script>
|
|
<!-- Additional helper script for common checks across event types -->
|
|
<script type="text/javascript" src="pointerevent_support.js"></script>
|
|
<script>
|
|
let frameLoaded = undefined;
|
|
const frameLoadedPromise = new Promise(resolve => {
|
|
frameLoaded = resolve;
|
|
});
|
|
</script>
|
|
<body>
|
|
<div id="square1"></div>
|
|
<div>
|
|
<iframe onLoad = "frameLoaded()" id="innerFrame" srcdoc='
|
|
<style>
|
|
html {
|
|
touch-action: none;
|
|
}
|
|
#square2 {
|
|
background-color: green;
|
|
border: 1px solid black;
|
|
height: 50px;
|
|
width: 50px;
|
|
display: inline-block;
|
|
}
|
|
</style>
|
|
<body>
|
|
<div id="square2"></div>
|
|
</body>
|
|
'></iframe>
|
|
</div>
|
|
<!-- Used to detect a sentinel event. Once triggered, all other events must
|
|
have been processed. -->
|
|
<div>
|
|
<button id="done">done</button>
|
|
</div>
|
|
</body>
|
|
<script>
|
|
window.onload = runTests();
|
|
|
|
async function runTests() {
|
|
|
|
const queryStringFragments = location.search.substring(1).split('-');
|
|
const pointerType = queryStringFragments[0];
|
|
const button = queryStringFragments[1] === "right" ? "right" : undefined;
|
|
const standard = !(queryStringFragments[queryStringFragments.length - 1] === "nonstandard");
|
|
|
|
const eventList = [
|
|
'pointerover',
|
|
'pointerenter',
|
|
'pointerdown',
|
|
'pointerup',
|
|
'pointerout',
|
|
'pointerleave',
|
|
'pointermove'
|
|
];
|
|
|
|
function injectScrubGesture(element) {
|
|
const doneButton = document.getElementById('done');
|
|
const actions = new test_driver.Actions();
|
|
|
|
let buttonArguments =
|
|
(button == 'right') ? { button: actions.ButtonType.RIGHT }
|
|
: undefined;
|
|
|
|
// The following comments refer to the first event of each type since
|
|
// that is what is being validated in the test.
|
|
return actions
|
|
.addPointer('pointer1', pointerType)
|
|
// The pointermove, pointerover and pointerenter events will be
|
|
// triggered here with a hover pointer.
|
|
.pointerMove(0, -20, { origin: element })
|
|
// Pointerdown triggers pointerover, pointerenter with a non-hover
|
|
// pointer type.
|
|
.pointerDown(buttonArguments)
|
|
// This move triggers pointermove with a non-hover pointer-type.
|
|
.pointerMove(0, 20, { origin: element })
|
|
// The pointerout and pointerleave events are triggered here with a
|
|
// touch pointer.
|
|
.pointerUp(buttonArguments)
|
|
// An addition move outside of the target bounds is required to trigger
|
|
// pointerout & pointerleave events with a hover pointer.
|
|
.pointerMove(0, 0)
|
|
.send();
|
|
}
|
|
|
|
// Processing a click or tap on the done button is used to signal that all
|
|
// other events should have beem handled. This is used to catch unhandled
|
|
// events that would otherwise result in a timeout.
|
|
function clickOrTapDone() {
|
|
const doneButton = document.getElementById('done');
|
|
const pointerupPromise = getEvent('pointerup', doneButton);
|
|
const actionPromise = new test_driver.Actions()
|
|
.addPointer('pointer1', 'touch')
|
|
.pointerMove(0, 0, {origin: doneButton})
|
|
.pointerDown()
|
|
.pointerUp()
|
|
.send();
|
|
return actionPromise.then(pointerupPromise);
|
|
}
|
|
|
|
function verifyButtonAttributes(event) {
|
|
let downButton, upButton, downButtons, upButtons;
|
|
if (button == 'right') {
|
|
downButton = 2;
|
|
downButtons = 2;
|
|
upButton = 2;
|
|
upButtons = 0;
|
|
} else {
|
|
// defaults to left button click
|
|
downButton = 0;
|
|
downButtons = 1;
|
|
upButton = 0;
|
|
upButtons = 0;
|
|
}
|
|
const expectationsHover = {
|
|
// Pointer over, enter, and move are processed before the button press.
|
|
pointerover: { button: -1, buttons: 0 },
|
|
pointerenter: { button: -1, buttons: 0 },
|
|
pointermove: { button: -1, buttons: 0 },
|
|
// Button status changes on pointer down and up.
|
|
pointerdown: { button: downButton, buttons: downButtons },
|
|
pointerup: { button: upButton, buttons: upButtons },
|
|
// Pointer out and leave are processed after the button release.
|
|
pointerout: { button: -1, buttons: 0 },
|
|
pointerleave: { button: -1, buttons: 0 }
|
|
};
|
|
const expectationsNoHover = {
|
|
// We don't see pointer events except during a touch gesture.
|
|
// Move is the only pointer event where the "button" click state is not
|
|
// changing. All other pointer events are associated with the start or
|
|
// end of a touch gesture.
|
|
pointerover: { button: 0, buttons: 1 },
|
|
pointerenter: { button: 0, buttons: 1 },
|
|
pointerdown: { button: 0, buttons: 1 },
|
|
pointermove: { button: -1, buttons: 1 },
|
|
pointerup: { button: 0, buttons: 0 },
|
|
pointerout: { button: 0, buttons: 0 },
|
|
pointerleave: { button: 0, buttons: 0 }
|
|
};
|
|
const expectations =
|
|
(pointerType == 'touch') ? expectationsNoHover : expectationsHover;
|
|
|
|
assert_equals(event.button, expectations[event.type].button,
|
|
`Button attribute on ${event.type}`);
|
|
assert_equals(event.buttons, expectations[event.type].buttons,
|
|
`Buttons attribute on ${event.type}`);
|
|
}
|
|
|
|
function verifyPosition(event) {
|
|
const boundingRect = event.target.getBoundingClientRect();
|
|
|
|
// With a touch pointer type, the pointerout and pointerleave will trigger
|
|
// on pointerup while clientX and clientY are still within the target's
|
|
// bounds. With a hover pointer, these events will be triggered only after
|
|
// clientX or clientY are out of the target's bounds.
|
|
if (pointerType != 'touch' &&
|
|
(event.type == 'pointerout' || event.type == 'pointerleave')) {
|
|
assert_true(
|
|
boundingRect.left > event.clientX ||
|
|
boundingRect.right < event.clientX ||
|
|
boundingRect.top > event.clientY ||
|
|
boundingRect.bottom < event.clientY,
|
|
`clientX/clientY is outside the element bounds for ${event.type} event`);
|
|
} else {
|
|
assert_true(
|
|
boundingRect.left <= event.clientX &&
|
|
boundingRect.right >= event.clientX,
|
|
`clientX is within the expected range for ${event.type} event`);
|
|
assert_true(
|
|
boundingRect.top <= event.clientY &&
|
|
boundingRect.bottom >= event.clientY,
|
|
`clientY is within the expected range for ${event.type} event`);
|
|
}
|
|
}
|
|
|
|
function verifyEventAttributes(event, testNamePrefix) {
|
|
verifyButtonAttributes(event);
|
|
verifyPosition(event);
|
|
assert_true(event.isPrimary, 'isPrimary attribute is true');
|
|
check_PointerEvent(event, testNamePrefix, standard);
|
|
}
|
|
|
|
function pointerPromise(test, testNamePrefix, type, target) {
|
|
let rejectCallback = undefined;
|
|
promise = new Promise((resolve, reject) => {
|
|
// Store a reference to the promise rejection functions, which would
|
|
// otherwise not be visible outside the promise object. If the callback
|
|
// remains set when the deadline is reached, it means that the promise
|
|
// will not get resolved and should be rejected.
|
|
rejectCallback = reject;
|
|
const pointerEventListener = event => {
|
|
rejectCallback = undefined;
|
|
assert_equals(event.type, type, `type attribute for ${type} event`);
|
|
event.preventDefault();
|
|
resolve(event);
|
|
};
|
|
target.addEventListener(type, pointerEventListener, { once: true });
|
|
test.add_cleanup(() => {
|
|
// Just in case of an assert prior to the events being triggered.
|
|
document.removeEventListener(type, pointerEventListener,
|
|
{ once: true });
|
|
});
|
|
}).then(result => { verifyEventAttributes(result, testNamePrefix); },
|
|
error => { assert_unreached(error); });
|
|
promise.deadlineReached = () => {
|
|
// If the event has not been received, the promise will not be
|
|
// fulfilled, leading to a timeout. Reject the promise if still pending.
|
|
if (rejectCallback) {
|
|
rejectCallback(`missing ${type} event`);
|
|
}
|
|
}
|
|
return promise;
|
|
}
|
|
|
|
async function runPointerEventsTest(test, testNamePrefix, target) {
|
|
assert_true(['mouse', 'pen', 'touch'].indexOf(pointerType) >= 0,
|
|
`Unexpected pointer type (${pointerType})`);
|
|
|
|
const promises = [];
|
|
eventList.forEach(type => {
|
|
// Create a promise for each event type. If clicking on the done button
|
|
// is detected before an event's promise is resolved, then the promise
|
|
// will be rejected. Otherwise, the attributes for the event are
|
|
// verified.
|
|
promises.push(pointerPromise(test, testNamePrefix, type, target));
|
|
});
|
|
|
|
await injectScrubGesture(target);
|
|
|
|
// The injected gestures consist of a shrub on a button followed by a
|
|
// click on the done button. The promise is only resolved after the
|
|
// done click is detected. At this stage all other events must have been
|
|
// processed. Any unresolved promises in the list will be rejected to
|
|
// avoid a test timeout. The rejection will trigger a test failure.
|
|
await clickOrTapDone().then(promises.map(p => p.deadlineReached()));
|
|
|
|
// Once all promises are resolved, all event attributes have been
|
|
// successfully verified.
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
promise_test(t => {
|
|
const square1 = document.getElementById('square1');
|
|
return runPointerEventsTest(t, '', square1);
|
|
}, 'Test pointer events in the main document');
|
|
|
|
promise_test(async t => {
|
|
const innerFrame = document.getElementById('innerFrame');
|
|
await frameLoadedPromise;
|
|
const square2 = innerFrame.contentDocument.getElementById('square2');
|
|
return runPointerEventsTest(t, 'Inner Frame', square2);
|
|
}, 'Test pointer events in an iframe');
|
|
}
|
|
</script>
|