Bug 1556703 - Part 1: Update active pointer state properly; r=smaug

Calling setPointerCapture or releasePointerCapture in pointerup event handler would
throw error due to the active pointer is removed in PreHandlerEvent which is too
early. So defer removing active pointer to PostHandeEvent to make the active pointer
available in pointerup or pointercancel event handler.

This patch adds WPTs which has been verified on Chrome. geckodriver doesn't support
touch input yet, so mark tests for touch as expected ERROR for now.

Differential Revision: https://phabricator.services.mozilla.com/D101667
This commit is contained in:
Edgar Chen 2021-01-18 22:14:39 +00:00
parent 2ac3b069ae
commit fcbd3d2887
9 changed files with 422 additions and 3 deletions

View File

@ -571,8 +571,6 @@ nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
}
}
PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
switch (aEvent->mMessage) {
case eContextMenu:
if (sIsPointerLocked) {
@ -624,6 +622,7 @@ nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
break;
}
case eMouseEnterIntoWidget:
PointerEventHandler::UpdateActivePointerState(mouseEvent, aTargetContent);
// In some cases on e10s eMouseEnterIntoWidget
// event was sent twice into child process of content.
// (From specific widget code (sending is not permanent) and
@ -679,6 +678,8 @@ nsresult EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
case eMouseMove:
case ePointerDown:
if (aEvent->mMessage == ePointerDown) {
PointerEventHandler::UpdateActivePointerState(mouseEvent,
aTargetContent);
PointerEventHandler::ImplicitlyCapturePointer(aTargetFrame, aEvent);
if (mouseEvent->mInputSource != MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
NotifyTargetUserActivation(aEvent, aTargetContent);
@ -3411,6 +3412,7 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
// Implicitly releasing capture for given pointer. ePointerLostCapture
// should be send after ePointerUp or ePointerCancel.
PointerEventHandler::ImplicitlyReleasePointerCapture(pointerEvent);
PointerEventHandler::UpdateActivePointerState(pointerEvent);
if (pointerEvent->mMessage == ePointerCancel ||
pointerEvent->mInputSource == MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
@ -3812,6 +3814,10 @@ nsresult EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
}
break;
case eMouseExitFromWidget:
PointerEventHandler::UpdateActivePointerState(aEvent->AsMouseEvent());
break;
#ifdef XP_MACOSX
case eMouseActivate:
if (mCurrentTarget) {

View File

@ -122,6 +122,7 @@ void PointerEventHandler::UpdateActivePointerState(WidgetMouseEvent* aEvent,
sActivePointersIds->Remove(aEvent->pointerId);
break;
default:
MOZ_ASSERT_UNREACHABLE("event has invalid type");
break;
}
}

View File

@ -73,7 +73,7 @@ class PointerEventHandler final {
// Called in ESM::PreHandleEvent to update current active pointers in a hash
// table.
static void UpdateActivePointerState(WidgetMouseEvent* aEvent,
nsIContent* aTargetContent);
nsIContent* aTargetContent = nullptr);
// Request/release pointer capture of the specified pointer by the element.
static void RequestPointerCaptureById(uint32_t aPointerId,

View File

@ -0,0 +1,3 @@
[pointerevent_releasepointercapture_pointerup_touch.html]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1543337
expected: ERROR

View File

@ -0,0 +1,3 @@
[pointerevent_setpointercapture_pointerup_touch.html]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1543337
expected: ERROR

View File

@ -0,0 +1,103 @@
<!doctype html>
<html>
<head>
<title>releasePointerCapture on pointerup</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>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="pointerevent_support.js"></script>
</head>
<body>
<h1>Pointer Events Capture Test - releasePointerCapture on pointerup</h1>
<h4>
Test Description: This test checks if releasePointerCapture works properly. Complete the following actions:
<ol>
<li> Press and hold left mouse button over "Set Capture" button
<li> Release left mouse button anywhere over the document
</ol>
</h4>
Test passes if the proper behavior of the events is observed.
<div id="target0" style="background:black; color:white"></div>
<br>
<input type="button" id="btnCapture" value="Set Capture">
<script type='text/javascript'>
var isPointerCapture = false;
var pointerupGot = false;
var count=0;
var event_log = [];
var actions_promise;
var detected_pointertypes = {};
add_completion_callback(end_of_test);
function end_of_test() {
showLoggedEvents();
showPointerTypes();
}
var target0 = document.getElementById('target0');
var captureButton = document.getElementById('btnCapture');
setup({ explicit_done: true });
window.onload = function() {
on_event(captureButton, 'pointerdown', function(e) {
detected_pointertypes[e.pointerType] = true;
if(isPointerCapture == false) {
isPointerCapture = true;
sPointerCapture(e);
pointerupGot = false;
}
});
on_event(target0, 'gotpointercapture', function(e) {
event_log.push('gotpointercapture@target0');
});
on_event(target0, 'lostpointercapture', function(e) {
test(function() {
assert_true(pointerupGot, "pointerup was received before lostpointercapture")
}, "pointerup was received before lostpointercapture");
event_log.push('lostpointercapture@target0');
isPointerCapture = false;
// Make sure the test finishes after all the input actions are completed.
actions_promise.then( () => {
done();
});
});
on_event(target0, 'pointerup', function(e) {
event_log.push('pointerup@target0');
var thrown = false;
try {
target0.releasePointerCapture(e.pointerId);
} catch(e) {
thrown = true;
}
test(function() {
assert_false(thrown, "target0.releasePointerCapture should not throw");
}, "target0.releasePointerCapture should not throw");
pointerupGot = true;
});
// Inject mouse inputs.
actions_promise = new test_driver.Actions()
.pointerMove(0, 0, {origin: captureButton})
.pointerDown()
.pointerMove(10, 0, {origin: target0})
.pointerUp()
.send();
}
</script>
<h1>Pointer Events Capture Test</h1>
<div id="complete-notice">
<p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>The following events were logged: <span id="event-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -0,0 +1,102 @@
<!doctype html>
<html>
<head>
<title>releasePointerCapture on pointerup</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>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="pointerevent_support.js"></script>
</head>
<body class="scrollable">
<h1>Pointer Events Capture Test - releasePointerCapture on pointerup</h1>
<h4>
Test Description: This test checks if releaseCapture works properly on pointer up. Complete the following actions:
<ol>
<li> Touch black rectangle and do not release your touch
<li> Move and release your touch anywhere over the document
</ol>
</h4>
Test passes if the proper behavior of the events is observed.
<div id="target0" style="background:black; color:white"></div>
<script type='text/javascript'>
var pointerupGot = false;
var count=0;
var event_log = [];
var detected_pointertypes = {};
var test_pointerEvent = async_test("releasePointerCapture on pointerup");
var target0 = document.getElementById('target0');
var actions_promise;
add_completion_callback(end_of_test);
function end_of_test() {
showLoggedEvents();
showPointerTypes();
}
window.onload = function() {
on_event(target0, 'pointerdown', function(e) {
detected_pointertypes[e.pointerType] = true;
test_pointerEvent.step(function () {
assert_equals(e.pointerType, "touch", "Test should be run using a touch as input");
});
sPointerCapture(e);
pointerupGot = false;
});
on_event(target0, 'gotpointercapture', function(e) {
event_log.push('gotpointercapture@target0');
});
on_event(target0, 'lostpointercapture', function(e) {
event_log.push('lostpointercapture@target0');
test_pointerEvent.step(function () {
assert_true(pointerupGot, "pointerup was received before lostpointercapture");
});
// Make sure the test finishes after all the input actions are completed.
actions_promise.then( () => {
test_pointerEvent.done();
});
});
on_event(target0, 'pointerup', function(e) {
event_log.push('pointerup@target0');
try {
target0.releasePointerCapture(e.pointerId);
} catch(error) {
test_pointerEvent.step(function () {
assert_unreached("target0.releasePointerCapture should not throw");
});
}
pointerupGot = true;
});
on_event(target0, 'touchmove', function(e) {
// To prevent pointercancel firing.
e.preventDefault();
});
on_event(target0, 'pointercancel', function(e) {
test_pointerEvent.step(function () {
assert_unreached("target0 shouldn't receive pointercancel");
});
});
// Inject touch inputs.
actions_promise = touchScrollInTarget(target0, 'down');
}
</script>
<h1>Pointer Events Capture Test</h1>
<div id="complete-notice">
<p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>The following events were logged: <span id="event-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -0,0 +1,99 @@
<!doctype html>
<html>
<head>
<title>setPointerCapture on pointerup</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>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="pointerevent_support.js"></script>
</head>
<body>
<h1>Pointer Events Capture Test - setPointerCapture on pointerup</h1>
<h4>
Test Description: This test checks if setPointerCapture works properly. Complete the following actions:
<ol>
<li> Press and hold left mouse button over black rectangle
<li> Release left mouse button
</ol>
</h4>
Test passes if the proper behavior of the events is observed.
<div id="target0" style="background:black; color:white"></div>
<br>
<script type='text/javascript'>
var count=0;
var event_log = [];
var actions_promise;
var detected_pointertypes = {};
add_completion_callback(end_of_test);
function end_of_test() {
showLoggedEvents();
showPointerTypes();
}
var target0 = document.getElementById('target0');
setup({ explicit_done: true });
window.onload = function() {
on_event(target0, 'pointerdown', function(e) {
event_log.push('pointerdown@target0');
detected_pointertypes[e.pointerType] = true;
test(function () {
assert_equals(event.pointerType, "mouse", "Test should be run using a mouse as input");
}, "Test should be run using a mouse as input");
});
on_event(target0, 'gotpointercapture', function(e) {
event_log.push('gotpointercapture@target0');
test(function () {
assert_true(false, "target0 shouldn't receive gotpointercapture");
}, "target0 shouldn't receive gotpointercapture");
});
on_event(target0, 'lostpointercapture', function(e) {
event_log.push('lostpointercapture@target0');
test(function () {
assert_true(false, "target0 shouldn't receive lostpointercapture");
}, "target0 shouldn't receive lostpointercapture");
});
on_event(target0, 'pointerup', function(e) {
event_log.push('pointerup@target0');
var thrown = false;
try {
target0.setPointerCapture(e.pointerId);
} catch(e) {
thrown = true;
}
test(function() {
assert_false(thrown, "target0.setPointerCapture should not throw");
}, "target0.setPointerCapture should not throw");
// Make sure the test finishes after all the input actions are completed.
actions_promise.then( () => {
done();
});
});
// Inject mouse inputs.
actions_promise = new test_driver.Actions()
.pointerMove(0, 0, {origin: target0})
.pointerDown()
.pointerMove(10, 0, {origin: target0})
.pointerUp()
.send();
}
</script>
<h1>Pointer Events Capture Test</h1>
<div id="complete-notice">
<p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>The following events were logged: <span id="event-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>

View File

@ -0,0 +1,102 @@
<!doctype html>
<html>
<head>
<title>setPointerCapture on pointerup</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>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script src="pointerevent_support.js"></script>
</head>
<body class="scrollable">
<h1>Pointer Events Capture Test - setPointerCapture on pointerup</h1>
<h4>
Test Description: This test checks if releaseCapture works properly on pointer up. Complete the following actions:
<ol>
<li> Touch black rectangle
<li> Release your touch
</ol>
</h4>
Test passes if the proper behavior of the events is observed.
<div id="target0" style="background:black; color:white"></div>
<script type='text/javascript'>
var count=0;
var event_log = [];
var detected_pointertypes = {};
var test_pointerEvent = async_test("setPointerCapture on pointerup");
var target0 = document.getElementById('target0');
var actions_promise;
add_completion_callback(end_of_test);
function end_of_test() {
showLoggedEvents();
showPointerTypes();
}
window.onload = function() {
on_event(target0, 'pointerdown', function(e) {
detected_pointertypes[e.pointerType] = true;
event_log.push('pointerdown@target0');
test_pointerEvent.step(function () {
assert_equals(e.pointerType, "touch", "Test should be run using a touch as input");
});
});
on_event(target0, 'gotpointercapture', function(e) {
event_log.push('gotpointercapture@target0');
});
on_event(target0, 'lostpointercapture', function(e) {
event_log.push('lostpointercapture@target0');
});
on_event(target0, 'pointerup', function(e) {
event_log.push('pointerup@target0');
try {
target0.setPointerCapture(e.pointerId);
} catch(error) {
test_pointerEvent.step(function () {
assert_unreached("target0.setPointerCapture should not throw");
});
}
// Make sure the test finishes after all the input actions are completed.
actions_promise.then( () => {
test_pointerEvent.done();
});
});
on_event(target0, 'touchmove', function(e) {
// To prevent pointercancel firing.
e.preventDefault();
});
on_event(target0, 'pointercancel', function(e) {
test_pointerEvent.step(function () {
assert_unreached("target0 shouldn't receive pointercancel");
});
});
// Inject touch inputs.
actions_promise = new test_driver.Actions()
.addPointer("touchPointer1", "touch")
.pointerMove(10, 10, {origin: target0})
.pointerDown()
.pause(100)
.pointerUp()
.send();
}
</script>
<h1>Pointer Events Capture Test</h1>
<div id="complete-notice">
<p>Test complete: Scroll to Summary to view Pass/Fail Results.</p>
<p>The following pointer types were detected: <span id="pointertype-log"></span>.</p>
<p>The following events were logged: <span id="event-log"></span>.</p>
</div>
<div id="log"></div>
</body>
</html>