Bug 1725493 - Make coalesced touchmove event could be up-to-date; r=smaug,sefeng

`mChanged` isn't propagated through IPC, so we end up never update the coalesced
event.  There can't be too many of touches, so it shouldn't be a performance
issue if we just update all touches.

Differential Revision: https://phabricator.services.mozilla.com/D122539
This commit is contained in:
Edgar Chen 2021-08-18 16:02:49 +00:00
parent e8beefa0ac
commit 9a4b0f2c7f
6 changed files with 251 additions and 36 deletions

View File

@ -101,8 +101,7 @@
ok(receivedSingleTouch, "Should've got single touch"); ok(receivedSingleTouch, "Should've got single touch");
ok(receivedMultiTouch, "Should've got multi touch"); ok(receivedMultiTouch, "Should've got multi touch");
opener.setTimeout("SimpleTest.finish()"); opener.finish();
window.close(); window.close();
} }

View File

@ -0,0 +1,189 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<title>touchmove coalescing</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
<script>
window.oncontextmenu = function(e) {
e.preventDefault();
}
window.addEventListener("touchstart", function(e) { e.preventDefault(); },
{ passive: false} );
var touchmoveEvents = [];
function touchmove(e) {
// Make touchmove handling slow
var start = performance.now();
while (performance.now() < (start + 5));
touchmoveEvents.push(e);
}
async function fireLotsOfSingleTouchMoves() {
var ret = new Promise(function(resolve) {
SpecialPowers.loadChromeScript(function() {
var element = this.actorParent.rootFrameLoader.ownerElement;
var rect = element.getBoundingClientRect();
var win = element.ownerDocument.defaultView;
var utils = win.windowUtils;
var x = rect.x + (rect.width / 2);
var y = Math.floor(rect.y + (rect.height / 4));
var endY = Math.floor(rect.y + ((rect.height / 4) * 2));
utils.sendTouchEvent("touchstart", [0], [x], [y], [1], [1], [0], [1],
0, false);
while (y != endY) {
utils.sendTouchEvent("touchmove", [0], [x], [y], [1], [1], [0], [1],
0, false);
++y;
}
utils.sendTouchEvent("touchend", [0], [x], [y], [1], [1], [0], [1],
0, false);
});
touchmoveEvents = [];
window.addEventListener("touchmove", touchmove, true);
window.addEventListener("touchend", function(e) {
window.removeEventListener("touchmove", touchmove, true);
resolve(touchmoveEvents);
}, {once: true});
});
return ret
}
async function fireTwoSingleTouches() {
var ret = new Promise(function(resolve) {
SpecialPowers.loadChromeScript(function() {
var element = this.actorParent.rootFrameLoader.ownerElement;
var rect = element.getBoundingClientRect();
var win = element.ownerDocument.defaultView;
var utils = win.windowUtils;
var x = rect.x + (rect.width / 2);
var startY = Math.floor(rect.y + (rect.height / 4));
var endY = Math.floor(rect.y + ((rect.height / 4) * 2));
utils.sendTouchEvent("touchstart", [0], [x], [startY], [1], [1], [0],
[1], 0, false);
utils.sendTouchEvent("touchmove", [0], [x], [startY], [1], [1], [0],
[1], 0, false);
utils.sendTouchEvent("touchmove", [0], [x], [startY + 1], [1], [1], [0],
[1], 0, false);
utils.sendTouchEvent("touchend", [0], [x], [endY], [1], [1], [0],
[1], 0, false);
});
touchmoveEvents = [];
window.addEventListener("touchmove", touchmove, true);
window.addEventListener("touchend", function(e) {
window.removeEventListener("touchmove", touchmove, true);
resolve(touchmoveEvents);
}, {once: true});
});
return ret
}
async function fireLotsOfMultiTouchMoves() {
var ret = new Promise(function(resolve) {
SpecialPowers.loadChromeScript(function() {
var element = this.actorParent.rootFrameLoader.ownerElement;
var rect = element.getBoundingClientRect();
var win = element.ownerDocument.defaultView;
var utils = win.windowUtils;
var x = rect.x + (rect.width / 2);
var startY = Math.floor(rect.y + (rect.height / 4));
var endY = Math.floor(rect.y + ((rect.height / 4) * 2));
utils.sendTouchEvent("touchstart", [0, 1], [x, x + 1],
[startY, startY + 1], [1, 1], [1, 1], [0, 0],
[1, 1], 0, false);
while (startY != endY) {
utils.sendTouchEvent("touchmove", [0, 1], [x, x + 1],
[startY, startY + 1], [1, 1], [1, 1], [0, 0],
[1, 1], 0, false);
++startY;
}
utils.sendTouchEvent("touchend", [0, 1], [x, x + 1], [endY, endY + 1],
[1, 1], [1, 1], [0, 0], [1, 1], 0, false);
});
touchmoveEvents = [];
window.addEventListener("touchmove", touchmove, true);
window.addEventListener("touchend", function(e) {
window.removeEventListener("touchmove", touchmove, true);
resolve(touchmoveEvents);
}, {once: true});
});
return ret
}
function disableCoalescing() {
return SpecialPowers.pushPrefEnv({"set": [["dom.events.compress.touchmove",
false],
["dom.events.coalesce.touchmove",
false]]});
}
function enableCoalescing() {
return SpecialPowers.pushPrefEnv({"set": [["dom.events.compress.touchmove",
false],
["dom.events.coalesce.touchmove",
true]]});
}
async function runTests() {
document.body.clientTop; // Flush layout
await disableCoalescing();
var touchMovesWithoutCoalescing = await fireLotsOfSingleTouchMoves();
await enableCoalescing();
var touchMovesWithCoalescing = await fireLotsOfSingleTouchMoves();
opener.ok(touchMovesWithoutCoalescing.length > touchMovesWithCoalescing.length,
"Coalescing should reduce the number of touchmove events");
opener.isDeeply(touchMovesWithoutCoalescing.shift().touches,
touchMovesWithCoalescing.shift().touches,
"Shouldn't have coalesced the initial touchmove");
opener.isDeeply(touchMovesWithoutCoalescing.pop().touches,
touchMovesWithCoalescing.pop().touches,
"The last touchmove event should be up-to-date");
await disableCoalescing();
var twoTouchMovesWithoutCoalescing = await fireTwoSingleTouches();
await enableCoalescing();
var twoTouchMovesWithCoalescing = await fireTwoSingleTouches();
opener.is(twoTouchMovesWithoutCoalescing.length, 2,
"Should have got two touchmoves");
opener.is(twoTouchMovesWithoutCoalescing.length,
twoTouchMovesWithCoalescing.length,
"Shouldn't have coalesced the touchmove.");
await disableCoalescing();
var multiTouchMovesWithoutCoalescing = await fireLotsOfMultiTouchMoves();
await enableCoalescing();
var multiTouchMovesWithCoalescing = await fireLotsOfMultiTouchMoves();
opener.ok(multiTouchMovesWithoutCoalescing.length > multiTouchMovesWithCoalescing.length,
"Coalescing should reduce the number of multitouch touchmove events");
opener.isDeeply(multiTouchMovesWithoutCoalescing.shift().touches,
multiTouchMovesWithCoalescing.shift().touches,
"Shouldn't have coalesced the initial multitouch touchmove event");
opener.isDeeply(multiTouchMovesWithoutCoalescing.pop().touches,
multiTouchMovesWithCoalescing.pop().touches,
"The last multitouch touchmove event should be up-to-date");
opener.finish();
window.close();
}
function init() {
SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", true]]},
runTests);
}
</script>
</head>
<body style="height: 100vh;" onload="SimpleTest.waitForFocus(init);">
</body>
</html>

View File

@ -14,12 +14,12 @@
window.addEventListener("touchstart", function(e) { e.preventDefault(); }, window.addEventListener("touchstart", function(e) { e.preventDefault(); },
{ passive: false} ); { passive: false} );
var touchmoveCount = 0; var touchmoveEvents = [];
function touchmove() { function touchmove(e) {
// Make touchmove handling slow // Make touchmove handling slow
var start = performance.now(); var start = performance.now();
while (performance.now() < (start + 10)); while (performance.now() < (start + 10));
++touchmoveCount; touchmoveEvents.push(e);
} }
async function fireLotsOfSingleTouchMoves() { async function fireLotsOfSingleTouchMoves() {
@ -44,11 +44,11 @@
}); });
touchmoveCount = 0; touchmoveEvents = [];
window.addEventListener("touchmove", touchmove, true); window.addEventListener("touchmove", touchmove, true);
window.addEventListener("touchend", function(e) { window.addEventListener("touchend", function(e) {
window.removeEventListener("touchmove", touchmove, true); window.removeEventListener("touchmove", touchmove, true);
resolve(touchmoveCount); resolve(touchmoveEvents);
}, {once: true}); }, {once: true});
}); });
@ -75,11 +75,11 @@
[1], 0, false); [1], 0, false);
}); });
touchmoveCount = 0; touchmoveEvents = [];
window.addEventListener("touchmove", touchmove, true); window.addEventListener("touchmove", touchmove, true);
window.addEventListener("touchend", function(e) { window.addEventListener("touchend", function(e) {
window.removeEventListener("touchmove", touchmove, true); window.removeEventListener("touchmove", touchmove, true);
resolve(touchmoveCount); resolve(touchmoveEvents);
}, {once: true}); }, {once: true});
}); });
@ -110,58 +110,71 @@
}); });
touchmoveCount = 0; touchmoveEvents = [];
window.addEventListener("touchmove", touchmove, true); window.addEventListener("touchmove", touchmove, true);
window.addEventListener("touchend", function(e) { window.addEventListener("touchend", function(e) {
window.removeEventListener("touchmove", touchmove, true); window.removeEventListener("touchmove", touchmove, true);
resolve(touchmoveCount); resolve(touchmoveEvents);
}, {once: true}); }, {once: true});
}); });
return ret return ret
} }
function disableCoalescing() { function disableIPCCompression() {
return SpecialPowers.pushPrefEnv({"set": [["dom.events.compress.touchmove", return SpecialPowers.pushPrefEnv({"set": [["dom.events.compress.touchmove",
false], false],
["dom.events.coalesce.touchmove", ["dom.events.coalesce.touchmove",
false]]}); false]]});
} }
function enableCoalescing() { function enableIPCCompression() {
return SpecialPowers.pushPrefEnv({"set": [["dom.events.compress.touchmove", return SpecialPowers.pushPrefEnv({"set": [["dom.events.compress.touchmove",
true], true],
["dom.events.coalesce.touchmove", ["dom.events.coalesce.touchmove",
true]]}); false]]});
} }
async function runTests() { async function runTests() {
document.body.clientTop; // Flush layout document.body.clientTop; // Flush layout
await disableCoalescing(); await disableIPCCompression();
var touchMovesWithoutCoalescing = await fireLotsOfSingleTouchMoves(); var touchMovesWithoutCoalescing = await fireLotsOfSingleTouchMoves();
await enableCoalescing(); await enableIPCCompression();
var touchMovesWithCoalescing = await fireLotsOfSingleTouchMoves(); var touchMovesWithCoalescing = await fireLotsOfSingleTouchMoves();
opener.ok(touchMovesWithoutCoalescing > touchMovesWithCoalescing, opener.ok(touchMovesWithoutCoalescing.length > touchMovesWithCoalescing.length,
"Coalescing should reduce the number of touchmove events"); "Coalescing should reduce the number of touchmove events");
opener.isDeeply(touchMovesWithoutCoalescing.shift().touches,
touchMovesWithCoalescing.shift().touches,
"Shouldn't have coalesced the initial touchmove");
opener.isDeeply(touchMovesWithoutCoalescing.pop().touches,
touchMovesWithCoalescing.pop().touches,
"The last touchmove event should be up-to-date");
await disableCoalescing(); await disableIPCCompression();
var twoTouchMovesWithoutCoalescing = await fireTwoSingleTouches(); var twoTouchMovesWithoutCoalescing = await fireTwoSingleTouches();
await enableCoalescing(); await enableIPCCompression();
var twoTouchMovesWithCoalescing = await fireTwoSingleTouches(); var twoTouchMovesWithCoalescing = await fireTwoSingleTouches();
opener.is(twoTouchMovesWithoutCoalescing, 2, opener.is(twoTouchMovesWithoutCoalescing.length, 2,
"Should have got two touchmoves"); "Should have got two touchmoves");
opener.is(twoTouchMovesWithoutCoalescing, twoTouchMovesWithCoalescing, opener.is(twoTouchMovesWithoutCoalescing.length,
"Shouldn't have coalesced the initial touchmove."); twoTouchMovesWithCoalescing.length,
"Shouldn't have coalesced the touchmove.");
await disableCoalescing(); await disableIPCCompression();
var multiTouchMovesWithoutCoalescing = await fireLotsOfMultiTouchMoves(); var multiTouchMovesWithoutCoalescing = await fireLotsOfMultiTouchMoves();
await enableCoalescing(); await enableIPCCompression();
var multiTouchMovesWithCoalescing = await fireLotsOfMultiTouchMoves(); var multiTouchMovesWithCoalescing = await fireLotsOfMultiTouchMoves();
opener.ok(multiTouchMovesWithoutCoalescing > multiTouchMovesWithCoalescing, opener.ok(multiTouchMovesWithoutCoalescing.length > multiTouchMovesWithCoalescing.length,
"Coalescing should reduce the number of multitouch touchmove events"); "Coalescing should reduce the number of multitouch touchmove events");
opener.isDeeply(multiTouchMovesWithoutCoalescing.shift().touches,
multiTouchMovesWithCoalescing.shift().touches,
"Shouldn't have coalesced the initial multitouch touchmove event");
opener.isDeeply(multiTouchMovesWithoutCoalescing.pop().touches,
multiTouchMovesWithCoalescing.pop().touches,
"The last multitouch touchmove event should be up-to-date");
opener.finish_ipc(); opener.finish();
window.close(); window.close();
} }

View File

@ -137,6 +137,7 @@ skip-if =
support-files = support-files =
file_coalesce_touchmove_ipc.html file_coalesce_touchmove_ipc.html
file_coalesce_touchmove_browserchild.html file_coalesce_touchmove_browserchild.html
file_coalesce_touchmove_browserchild2.html
skip-if = debug #In order to be able to test touchmoves, the test needs to synthesize touchstart in a way which asserts skip-if = debug #In order to be able to test touchmoves, the test needs to synthesize touchstart in a way which asserts
[test_deviceSensor.html] [test_deviceSensor.html]
[test_bug812744.html] [test_bug812744.html]

View File

@ -8,15 +8,27 @@
<script> <script>
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
function finish_ipc() { let tests = [
window.open("file_coalesce_touchmove_browserchild.html"); "file_coalesce_touchmove_ipc.html",
"file_coalesce_touchmove_browserchild.html",
"file_coalesce_touchmove_browserchild2.html",
];
function finish() {
info("finish");
if (tests.length) {
SimpleTest.executeSoon(run);
return;
}
SimpleTest.finish();
} }
function start() { function run() {
window.open("file_coalesce_touchmove_ipc.html"); info(`run ${tests[0]}`);
window.open(tests.shift());
} }
</script> </script>
</head> </head>
<body onload="start();"> <body onload="run();">
</body> </body>
</html> </html>

View File

@ -28,11 +28,12 @@ void CoalescedTouchData::Coalesce(const WidgetTouchEvent& aEvent,
MOZ_ASSERT(mCoalescedInputEvent->mInputSource == aEvent.mInputSource); MOZ_ASSERT(mCoalescedInputEvent->mInputSource == aEvent.mInputSource);
for (const RefPtr<Touch>& touch : aEvent.mTouches) { for (const RefPtr<Touch>& touch : aEvent.mTouches) {
if (touch->mChanged) { // Get the same touch in the original event
// Get the same touch in the original event RefPtr<Touch> sameTouch = GetTouch(touch->Identifier());
RefPtr<Touch> sameTouch = GetTouch(touch->Identifier()); // The checks in CoalescedTouchData::CanCoalesce ensure it should never
sameTouch->SetSameAs(touch); // be null.
} MOZ_ASSERT(sameTouch);
sameTouch->SetSameAs(touch);
} }
mCoalescedInputEvent->mTimeStamp = aEvent.mTimeStamp; mCoalescedInputEvent->mTimeStamp = aEvent.mTimeStamp;