diff --git a/dom/events/test/mochitest.toml b/dom/events/test/mochitest.toml
index 9e00370eed6c..4a3de3792c82 100644
--- a/dom/events/test/mochitest.toml
+++ b/dom/events/test/mochitest.toml
@@ -668,6 +668,8 @@ skip-if = [
["test_text_event_in_content.html"]
+["test_touchmove_once_per_target.html"]
+
["test_unbound_before_in_active_chain.html"]
["test_unicode_input_on_windows_with_emulation.html"]
diff --git a/dom/events/test/test_mouse_events_after_touchend.html b/dom/events/test/test_mouse_events_after_touchend.html
index a55d8c7e4440..dcc296442975 100644
--- a/dom/events/test/test_mouse_events_after_touchend.html
+++ b/dom/events/test/test_mouse_events_after_touchend.html
@@ -221,18 +221,24 @@ SimpleTest.waitForFocus(async () => {
await promiseFlushingAPZGestureState();
events = [];
info("test_multi_touch: testing...");
+ // Both touch points are on the same target, so they are reported by a
+ // single touchend whose changedTouches contains both of them.
+ let endedTouches = 0;
const waitForTouchEnd = new Promise(resolve => {
- let count = 0;
function onTouchEnd(event) {
- if (++count == 2) {
- removeEventListener("touchend", onTouchEnd, {capture: true});
- requestAnimationFrame(() => requestAnimationFrame(resolve));
- }
+ endedTouches = event.changedTouches.length;
+ removeEventListener("touchend", onTouchEnd, {capture: true});
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
}
addEventListener("touchend", onTouchEnd, {capture: true});
});
synthesizeTouch(child, [5, 25], 5);
await waitForTouchEnd;
+ is(
+ endedTouches,
+ 2,
+ `Both touch points should be reported by the touchend ${desc}`
+ );
is(
stringifyEvents(shiftEventsBefore(events)),
stringifyEvents([]),
@@ -242,7 +248,6 @@ SimpleTest.waitForFocus(async () => {
stringifyEvents(events),
stringifyEvents([
{ type: "touchend", target: child },
- { type: "touchend", target: child },
]),
`Multiple touch should not cause mouse events ${desc}`
);
diff --git a/dom/events/test/test_touchmove_once_per_target.html b/dom/events/test/test_touchmove_once_per_target.html
new file mode 100644
index 000000000000..4096c24e985d
--- /dev/null
+++ b/dom/events/test/test_touchmove_once_per_target.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+Test for bug 2018625: a touch event is dispatched once per target
+
+
+
+
+
+
+
+
+
+
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
index 8b1f69b52380..f78806ec1f88 100644
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -9947,6 +9947,12 @@ void PresShell::EventHandler::DispatchTouchEventToDOM(
nsEventStatus tmpStatus = nsEventStatus_eIgnore;
WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ // A single widget event may carry more than one changed touch point. In
+ // that case only one DOM event should be dispatched per distinct target,
+ // carrying all the changed touch points, rather than one event per changed
+ // touch point.
+ AutoTArray, 4> dispatchedTargets;
+
// loop over all touches and dispatch events on any that have changed
for (dom::Touch* touch : touchEvent->mTouches) {
// We should remove all suppressed touch instances in
@@ -9972,6 +9978,12 @@ void PresShell::EventHandler::DispatchTouchEventToDOM(
}
content = capturingContent;
}
+
+ if (dispatchedTargets.Contains(targetPtr.get())) {
+ continue;
+ }
+ dispatchedTargets.AppendElement(targetPtr);
+
// copy the event
MOZ_ASSERT(touchEvent->IsTrusted());
WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget);
diff --git a/testing/web-platform/meta/touch-events/multi-touch-interactions.html.ini b/testing/web-platform/meta/touch-events/multi-touch-interactions.html.ini
index 739ef7a4452e..4e8ec5d5c6ce 100644
--- a/testing/web-platform/meta/touch-events/multi-touch-interactions.html.ini
+++ b/testing/web-platform/meta/touch-events/multi-touch-interactions.html.ini
@@ -1,14 +1,11 @@
[multi-touch-interactions.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
- [touchstart #2: change in touches.length is valid]
- expected: FAIL
-
[touchend #1: change in targetTouches.length is valid]
expected: FAIL
- [touchend #2: changedTouches is a subset of last received touches]
- expected: FAIL
-
[touchend #3: change in targetTouches.length is valid]
expected: FAIL
+
+ [touchend #2: change in targetTouches.length is valid]
+ expected: FAIL
diff --git a/testing/web-platform/tests/touch-events/mouseevents-after-touchend.tentative.html b/testing/web-platform/tests/touch-events/mouseevents-after-touchend.tentative.html
index 3032fadf1936..72fb924172cb 100644
--- a/testing/web-platform/tests/touch-events/mouseevents-after-touchend.tentative.html
+++ b/testing/web-platform/tests/touch-events/mouseevents-after-touchend.tentative.html
@@ -37,7 +37,12 @@ addEventListener("load", t => {
"touchend"]) {
if (type == "touchend") {
addEventListener(type, event => {
- events.push({type: type, target: event.target});
+ events.push({
+ type: type,
+ target: event.target,
+ changedTouches: event.changedTouches.length,
+ touches: event.touches.length,
+ });
}, {capture: true});
} else {
addEventListener(type, event => {
@@ -59,6 +64,12 @@ addEventListener("load", t => {
function stringifyEvent(event) {
return `{ type: ${event.type}, target: ${
event.target.id || event.target.nodeName
+ }${
+ event.changedTouches !== undefined
+ ? `, changedTouches: ${event.changedTouches}`
+ : ""
+ }${
+ event.touches !== undefined ? `, touches: ${event.touches}` : ""
}${
event.detail !== undefined ? `, detail: ${event.detail}` : ""
}${
@@ -99,7 +110,7 @@ addEventListener("load", t => {
assert_equals(
stringifyEvents(events),
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
@@ -123,7 +134,7 @@ addEventListener("load", t => {
assert_equals(
stringifyEvents(events),
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
])
);
}, "Single tap whose touchstart is consumed should not cause a click");
@@ -143,7 +154,7 @@ addEventListener("load", t => {
assert_equals(
stringifyEvents(events),
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
])
);
}, "Single tap whose touchend is consumed should not cause a click");
@@ -166,24 +177,24 @@ addEventListener("load", t => {
// double click, therefore, it's fine either single click twice or
// a set of a double-click.
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
{ type: "click", target: child, detail: 1, button: 0, buttons: 0 },
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
{ type: "click", target: child, detail: 1, button: 0, buttons: 0 },
]),
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
{ type: "click", target: child, detail: 1, button: 0, buttons: 0 },
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 2, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 2, button: 0, buttons: 0 },
@@ -209,12 +220,12 @@ addEventListener("load", t => {
assert_equals(
stringifyEvents(events),
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
{ type: "click", target: child, detail: 1, button: 0, buttons: 0 },
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
@@ -238,12 +249,12 @@ addEventListener("load", t => {
assert_equals(
stringifyEvents(events),
stringifyEvents([
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
{ type: "click", target: child, detail: 1, button: 0, buttons: 0 },
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 1, touches: 0 },
{ type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 },
{ type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 },
{ type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 },
@@ -265,11 +276,14 @@ addEventListener("load", t => {
.pointerUp({sourceName: "touchPointer"})
.pointerUp({sourceName: "touchPointer2"})
.send();
+ // Both pointers are released in the same tick, i.e. at the same moment, and
+ // both of them started on #child. Therefore a single touchend reporting
+ // both removed touch points is expected rather than one touchend per
+ // released pointer.
assert_equals(
stringifyEvents(events),
stringifyEvents([
- { type: "touchend", target: child },
- { type: "touchend", target: child },
+ { type: "touchend", target: child, changedTouches: 2, touches: 0 },
])
);
}, "Multi tap should not cause mouse events");