browser_touch_simulation.js (9608B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 // Test global touch simulation button 7 8 const TEST_URL = `${URL_ROOT_SSL}touch.html`; 9 const PREF_DOM_META_VIEWPORT_ENABLED = "dom.meta-viewport.enabled"; 10 11 // A 300ms delay between a `touchend` and `click` event is added whenever double-tap zoom 12 // is allowed. 13 const DELAY_MIN = 250; 14 15 addRDMTask(TEST_URL, async function ({ ui }) { 16 reloadOnTouchChange(true); 17 18 await waitBootstrap(ui); 19 await testWithNoTouch(ui); 20 await toggleTouchSimulation(ui); 21 await promiseContentReflow(ui); 22 await testWithTouch(ui); 23 await testWithMetaViewportEnabled(ui); 24 await testWithMetaViewportDisabled(ui); 25 testTouchButton(ui); 26 27 reloadOnTouchChange(false); 28 }); 29 30 async function testWithNoTouch(ui) { 31 await SpecialPowers.spawn(ui.getViewportBrowser(), [], async function () { 32 const div = content.document.querySelector("div"); 33 let x = 0, 34 y = 0; 35 36 info("testWithNoTouch: Initial test parameter and mouse mouse outside div"); 37 x = -1; 38 y = -1; 39 await EventUtils.synthesizeMouse( 40 div, 41 x, 42 y, 43 { type: "mousemove", isSynthesized: false }, 44 content 45 ); 46 div.style.transform = "none"; 47 div.style.backgroundColor = ""; 48 49 info("testWithNoTouch: Move mouse into the div element"); 50 await EventUtils.synthesizeMouseAtCenter( 51 div, 52 { type: "mousemove", isSynthesized: false }, 53 content 54 ); 55 is(div.style.backgroundColor, "red", "mouseenter or mouseover should work"); 56 57 info("testWithNoTouch: Drag the div element"); 58 await EventUtils.synthesizeMouseAtCenter( 59 div, 60 { type: "mousedown", isSynthesized: false }, 61 content 62 ); 63 x = 100; 64 y = 100; 65 await EventUtils.synthesizeMouse( 66 div, 67 x, 68 y, 69 { type: "mousemove", isSynthesized: false }, 70 content 71 ); 72 is(div.style.transform, "none", "touchmove shouldn't work"); 73 await EventUtils.synthesizeMouse( 74 div, 75 x, 76 y, 77 { type: "mouseup", isSynthesized: false }, 78 content 79 ); 80 81 info("testWithNoTouch: Move mouse out of the div element"); 82 x = -1; 83 y = -1; 84 await EventUtils.synthesizeMouse( 85 div, 86 x, 87 y, 88 { type: "mousemove", isSynthesized: false }, 89 content 90 ); 91 is(div.style.backgroundColor, "blue", "mouseout or mouseleave should work"); 92 93 info("testWithNoTouch: Click the div element"); 94 await EventUtils.synthesizeClick(div); 95 is( 96 div.dataset.isDelay, 97 "false", 98 "300ms delay between touch events and mouse events should not work" 99 ); 100 101 // Assuming that this test runs on devices having no touch screen device. 102 ok( 103 !content.matchMedia("(pointer: coarse)").matches, 104 "pointer: coarse shouldn't be matched" 105 ); 106 ok( 107 !content.matchMedia("(hover: none)").matches, 108 "hover: none shouldn't be matched" 109 ); 110 ok( 111 !content.matchMedia("(any-pointer: coarse)").matches, 112 "any-pointer: coarse shouldn't be matched" 113 ); 114 ok( 115 !content.matchMedia("(any-hover: none)").matches, 116 "any-hover: none shouldn't be matched" 117 ); 118 }); 119 } 120 121 async function testWithTouch(ui) { 122 await SpecialPowers.spawn(ui.getViewportBrowser(), [], async function () { 123 const div = content.document.querySelector("div"); 124 let x = 0, 125 y = 0; 126 127 info("testWithTouch: Initial test parameter and mouse mouse outside div"); 128 x = -1; 129 y = -1; 130 await EventUtils.synthesizeMouse( 131 div, 132 x, 133 y, 134 { type: "mousemove", isSynthesized: false }, 135 content 136 ); 137 div.style.transform = "none"; 138 div.style.backgroundColor = ""; 139 140 info("testWithTouch: Move mouse into the div element"); 141 await EventUtils.synthesizeMouseAtCenter( 142 div, 143 { type: "mousemove", isSynthesized: false }, 144 content 145 ); 146 isnot( 147 div.style.backgroundColor, 148 "red", 149 "mouseenter or mouseover should not work" 150 ); 151 152 info("testWithTouch: Drag the div element"); 153 await EventUtils.synthesizeMouseAtCenter( 154 div, 155 { type: "mousedown", isSynthesized: false }, 156 content 157 ); 158 x = 100; 159 y = 100; 160 const touchMovePromise = ContentTaskUtils.waitForEvent(div, "touchmove"); 161 await EventUtils.synthesizeMouse( 162 div, 163 x, 164 y, 165 { type: "mousemove", isSynthesized: false }, 166 content 167 ); 168 await touchMovePromise; 169 isnot(div.style.transform, "none", "touchmove should work"); 170 await EventUtils.synthesizeMouse( 171 div, 172 x, 173 y, 174 { type: "mouseup", isSynthesized: false }, 175 content 176 ); 177 178 info("testWithTouch: Move mouse out of the div element"); 179 x = -1; 180 y = -1; 181 await EventUtils.synthesizeMouse( 182 div, 183 x, 184 y, 185 { type: "mousemove", isSynthesized: false }, 186 content 187 ); 188 isnot( 189 div.style.backgroundColor, 190 "blue", 191 "mouseout or mouseleave should not work" 192 ); 193 194 ok( 195 content.matchMedia("(pointer: coarse)").matches, 196 "pointer: coarse should be matched" 197 ); 198 ok( 199 content.matchMedia("(hover: none)").matches, 200 "hover: none should be matched" 201 ); 202 ok( 203 content.matchMedia("(any-pointer: coarse)").matches, 204 "any-pointer: coarse should be matched" 205 ); 206 ok( 207 content.matchMedia("(any-hover: none)").matches, 208 "any-hover: none should be matched" 209 ); 210 }); 211 212 // Capturing touch events with the content window as a registered listener causes the 213 // "changedTouches" field to be undefined when using deprecated TouchEvent APIs. 214 // See Bug 1549220 and Bug 1588438 for more information on this issue. 215 info("Test that changed touches captured on the content window are defined."); 216 await SpecialPowers.spawn(ui.getViewportBrowser(), [], async function () { 217 const div = content.document.querySelector("div"); 218 219 content.addEventListener( 220 "touchstart", 221 event => { 222 const changedTouch = event.changedTouches[0]; 223 ok(changedTouch, "Changed touch is defined."); 224 }, 225 { once: true } 226 ); 227 await EventUtils.synthesizeClick(div); 228 }); 229 } 230 231 async function testWithMetaViewportEnabled(ui) { 232 await SpecialPowers.pushPrefEnv({ 233 set: [[PREF_DOM_META_VIEWPORT_ENABLED, true]], 234 }); 235 236 await SpecialPowers.spawn( 237 ui.getViewportBrowser(), 238 [{ delay_min: DELAY_MIN }], 239 async function ({ delay_min }) { 240 // A helper for testing the delay between touchend and click events. 241 async function testDelay(mvc, el) { 242 const touchendPromise = ContentTaskUtils.waitForEvent(el, "touchend"); 243 const clickPromise = ContentTaskUtils.waitForEvent(el, "click"); 244 await EventUtils.synthesizeClick(el); 245 const { timeStamp: touchendTimestamp } = await touchendPromise; 246 const { timeStamp: clickTimeStamp } = await clickPromise; 247 const delay = clickTimeStamp - touchendTimestamp; 248 249 const expected = delay >= delay_min; 250 251 ok( 252 expected, 253 `${mvc}: There should be greater than a ${delay_min}ms delay between touch events and mouse events. Got delay of ${delay}ms` 254 ); 255 } 256 257 // A helper function for waiting for reflow to complete. 258 const promiseReflow = () => { 259 return new Promise(resolve => { 260 content.window.requestAnimationFrame(() => { 261 content.window.requestAnimationFrame(resolve); 262 }); 263 }); 264 }; 265 266 const meta = content.document.querySelector("meta[name=viewport]"); 267 const div = content.document.querySelector("div"); 268 269 info( 270 "testWithMetaViewportEnabled: " + 271 "click the div element with <meta name='viewport'>" 272 ); 273 meta.content = ""; 274 await promiseReflow(); 275 await testDelay("(empty)", div); 276 } 277 ); 278 279 await SpecialPowers.popPrefEnv(); 280 } 281 282 async function testWithMetaViewportDisabled(ui) { 283 await SpecialPowers.pushPrefEnv({ 284 set: [[PREF_DOM_META_VIEWPORT_ENABLED, false]], 285 }); 286 287 await SpecialPowers.spawn( 288 ui.getViewportBrowser(), 289 [{ delay_min: DELAY_MIN }], 290 async function ({ delay_min }) { 291 const meta = content.document.querySelector("meta[name=viewport]"); 292 const div = content.document.querySelector("div"); 293 294 info( 295 "testWithMetaViewportDisabled: click the div with <meta name='viewport'>" 296 ); 297 meta.content = ""; 298 const touchendPromise = ContentTaskUtils.waitForEvent(div, "touchend"); 299 const clickPromise = ContentTaskUtils.waitForEvent(div, "click"); 300 await EventUtils.synthesizeClick(div); 301 const { timeStamp: touchendTimestamp } = await touchendPromise; 302 const { timeStamp: clickTimeStamp } = await clickPromise; 303 const delay = clickTimeStamp - touchendTimestamp; 304 305 const expected = delay >= delay_min; 306 307 ok( 308 expected, 309 `There should be greater than a ${delay_min}ms delay between touch events and mouse events. Got delay of ${delay}ms` 310 ); 311 } 312 ); 313 } 314 315 function testTouchButton(ui) { 316 const { document } = ui.toolWindow; 317 const touchButton = document.getElementById("touch-simulation-button"); 318 319 ok( 320 touchButton.classList.contains("checked"), 321 "Touch simulation is active at end of test." 322 ); 323 324 touchButton.click(); 325 326 ok( 327 !touchButton.classList.contains("checked"), 328 "Touch simulation is stopped on click." 329 ); 330 331 touchButton.click(); 332 333 ok( 334 touchButton.classList.contains("checked"), 335 "Touch simulation is started on click." 336 ); 337 } 338 339 async function waitBootstrap(ui) { 340 await waitForFrameLoad(ui, TEST_URL); 341 }