test_mouse_events_after_touchend.html (8466B)
1 <!doctype html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Tests for mouse events after touchend</title> 7 <script src="/tests/SimpleTest/SimpleTest.js"></script> 8 <script src="/tests/SimpleTest/EventUtils.js"></script> 9 <script src="/tests/SimpleTest/paint_listener.js"></script> 10 <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> 11 <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js"></script> 12 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 13 <style> 14 #parent, #child { 15 width: 300px; 16 height: 64px; 17 padding: 16px; 18 } 19 #parent { 20 background-color: black; 21 } 22 #child { 23 background-color: gray; 24 } 25 </style> 26 <script> 27 "use strict"; 28 29 SimpleTest.waitForExplicitFinish(); 30 SimpleTest.requestFlakyTimeout("Required for waiting to prevent double tap at second tap"); 31 SimpleTest.waitForFocus(async () => { 32 function stringifyEvent(event) { 33 return `{ type: ${event.type}, target: ${ 34 event.target.id || event.target.nodeName 35 }${ 36 event.detail !== undefined ? `, detail: ${event.detail}` : "" 37 }${ 38 event.button !== undefined ? `, button: ${event.button}` : "" 39 }${ 40 event.buttons !== undefined ? `, buttons: ${event.buttons}` : "" 41 } }`; 42 } 43 function stringifyEvents(arrayOfEvents) { 44 if (!arrayOfEvents.length) { 45 return "[]"; 46 } 47 let ret = ""; 48 for (const event of arrayOfEvents) { 49 if (ret === "") { 50 ret = "[ "; 51 } else { 52 ret += ", "; 53 } 54 ret += stringifyEvent(event); 55 } 56 return ret + " ]"; 57 } 58 59 let events = []; 60 for (const type of ["mousemove", 61 "mousedown", 62 "mouseup", 63 "click", 64 "dblclick", 65 "contextmenu", 66 "touchend"]) { 67 if (type == "touchend") { 68 addEventListener(type, event => { 69 info(`Received: ${stringifyEvent(event)}`); 70 events.push({type, target: event.target}); 71 }, {capture: true}); 72 } else { 73 addEventListener(type, event => { 74 info(`Received: ${stringifyEvent(event)}`); 75 events.push({ 76 type: event.type, 77 target: event.target, 78 detail: event.detail, 79 button: event.button, 80 buttons: event.buttons, 81 }); 82 }, {capture: true}); 83 } 84 } 85 86 function shiftEventsBefore(arrayOfEvents, aType) { 87 const index = arrayOfEvents.findIndex(event => event.type == aType); 88 if (index <= 0) { 89 return []; 90 } 91 let ret = []; 92 for (let i = 0; i < index; i++) { 93 ret.push(arrayOfEvents.shift()); 94 } 95 return ret; 96 } 97 98 const parent = document.getElementById("parent"); 99 const child = document.getElementById("child"); 100 101 function promiseEvent(aType) { 102 return new Promise(resolve => 103 addEventListener(aType, resolve, {once: true}) 104 ); 105 } 106 107 async function promiseFlushingAPZGestureState() { 108 await promiseApzFlushedRepaints(); 109 // Wait for a while to avoid that the next tap will be treated as 2nd tap of 110 // a double tap. 111 return new Promise( 112 resolve => setTimeout( 113 resolve, 114 // NOTE: x1.0 is not enough to avoid intermittent failures. 115 SpecialPowers.getIntPref("apz.max_tap_time") * 1.2 116 ) 117 ); 118 } 119 120 await waitUntilApzStable(); 121 for (const prefValue of [true, false]) { 122 await SpecialPowers.pushPrefEnv({ 123 set: [ 124 ["test.events.async.enabled", prefValue], 125 ["ui.click_hold_context_menus.delay", 15000], // disable long tap 126 ] 127 }); 128 const desc = `(test.events.async.enabled=${prefValue})`; 129 130 await (async function test_single_tap() { 131 await promiseFlushingAPZGestureState(); 132 info("test_single_tap: testing..."); 133 events = []; 134 const waitForClick = promiseEvent("click"); 135 synthesizeTouch(child, 5, 5); 136 await waitForClick; 137 is( 138 stringifyEvents(events), 139 stringifyEvents([ 140 { type: "touchend", target: child }, 141 { type: "mousemove", target: child, detail: 0, button: 0, buttons: 0 }, 142 { type: "mousedown", target: child, detail: 1, button: 0, buttons: 1 }, 143 { type: "mouseup", target: child, detail: 1, button: 0, buttons: 0 }, 144 { type: "click", target: child, detail: 1, button: 0, buttons: 0 }, 145 ]), 146 `Single tap should cause a click ${desc}` 147 ); 148 })(); 149 150 await (async function test_single_tap_with_consuming_pointerdown() { 151 await promiseFlushingAPZGestureState(); 152 info("test_single_tap_with_consuming_pointerdown: testing..."); 153 events = []; 154 const waitForTouchEnd = promiseEvent("click"); 155 child.addEventListener("pointerdown", event => { 156 event.preventDefault(); 157 }, {once: true}); 158 synthesizeTouch(child, 5, 5); 159 await waitForTouchEnd; 160 const result = stringifyEvents(events); 161 const expected = stringifyEvents([ 162 { type: "touchend", target: child }, 163 { type: "click", target: child, detail: 1, button: 0, buttons: 0 }, 164 ]); 165 // If testing on Windows, the result is really unstable. Let's allow to 166 // fail for now. 167 (navigator.platform.includes("Win") && result != expected ? todo_is : is)( 168 result, 169 expected, 170 `Single tap should not cause mouse events if pointerdown is consumed, but click event should be fired ${desc}` 171 ); 172 })(); 173 174 await (async function test_single_tap_with_consuming_touchstart() { 175 await promiseFlushingAPZGestureState(); 176 info("test_single_tap_with_consuming_touchstart: testing..."); 177 events = []; 178 const waitForTouchEnd = promiseEvent("touchend"); 179 child.addEventListener("touchstart", event => { 180 event.preventDefault(); 181 }, {once: true}); 182 synthesizeTouch(child, 5, 5); 183 await waitForTouchEnd; 184 const result = stringifyEvents(events); 185 const expected = stringifyEvents([{ type: "touchend", target: child }]); 186 // If testing this with APZ, the result is really unstable. Let's allow to 187 // fail for now. 188 (prefValue && result != expected ? todo_is : is)( 189 result, 190 expected, 191 `Single tap should not cause mouse events if touchstart is consumed ${desc}` 192 ); 193 })(); 194 195 196 await (async function test_single_tap_with_consuming_touchend() { 197 await promiseFlushingAPZGestureState(); 198 info("test_single_tap_with_consuming_touchend: testing..."); 199 events = []; 200 const waitForTouchEnd = promiseEvent("touchend"); 201 child.addEventListener("touchend", event => { 202 event.preventDefault(); 203 }, {once: true}); 204 synthesizeTouch(child, 5, 5); 205 await waitForTouchEnd; 206 is( 207 stringifyEvents(shiftEventsBefore(events)), 208 stringifyEvents([]), 209 `test_single_tap_with_consuming_touchstart() shouldn't cause mouse events after touchend` 210 ) 211 is( 212 stringifyEvents(events), 213 stringifyEvents([ 214 { type: "touchend", target: child }, 215 ]), 216 `Single tap should not cause mouse events if touchend is consumed ${desc}` 217 ); 218 })(); 219 220 await (async function test_multi_touch() { 221 await promiseFlushingAPZGestureState(); 222 events = []; 223 info("test_multi_touch: testing..."); 224 const waitForTouchEnd = new Promise(resolve => { 225 let count = 0; 226 function onTouchEnd(event) { 227 if (++count == 2) { 228 removeEventListener("touchend", onTouchEnd, {capture: true}); 229 requestAnimationFrame(() => requestAnimationFrame(resolve)); 230 } 231 } 232 addEventListener("touchend", onTouchEnd, {capture: true}); 233 }); 234 synthesizeTouch(child, [5, 25], 5); 235 await waitForTouchEnd; 236 is( 237 stringifyEvents(shiftEventsBefore(events)), 238 stringifyEvents([]), 239 `test_single_tap_with_consuming_touchend() shouldn't cause mouse events after touchend` 240 ) 241 is( 242 stringifyEvents(events), 243 stringifyEvents([ 244 { type: "touchend", target: child }, 245 { type: "touchend", target: child }, 246 ]), 247 `Multiple touch should not cause mouse events ${desc}` 248 ); 249 })(); 250 251 // FIXME: Add long tap tests which won't frequently fail. 252 } 253 SimpleTest.finish(); 254 }); 255 </script> 256 </head> 257 <body><div id="parent"><div id="child"></div></div></body> 258 </html>