pointerevent_attributes.html (11674B)
1 <!doctype html> 2 <html> 3 <head> 4 <title>Pointer Events properties tests</title> 5 <meta name="viewport" content="width=device-width"> 6 <meta name="variant" content="?mouse"> 7 <meta name="variant" content="?pen"> 8 <meta name="variant" content="?mouse-right"> 9 <meta name="variant" content="?pen-right"> 10 <meta name="variant" content="?touch"> 11 <meta name="variant" content="?mouse-nonstandard"> 12 <meta name="variant" content="?pen-nonstandard"> 13 <meta name="variant" content="?mouse-right-nonstandard"> 14 <meta name="variant" content="?pen-right-nonstandard"> 15 <meta name="variant" content="?touch-nonstandard"> 16 <link rel="stylesheet" type="text/css" href="pointerevent_styles.css"> 17 <style> 18 html { 19 touch-action: none; 20 } 21 22 div { 23 padding: 0; 24 } 25 26 #square1 { 27 background-color: green; 28 border: 1px solid black; 29 height: 50px; 30 width: 50px; 31 margin-bottom: 3px; 32 display: inline-block; 33 } 34 35 #innerFrame { 36 position: relative; 37 margin-bottom: 3px; 38 margin-left: 0; 39 top: 0; 40 left: 0; 41 } 42 </style> 43 </head> 44 <script src="/resources/testharness.js"></script> 45 <script src="/resources/testharnessreport.js"></script> 46 <script src="/resources/testdriver.js"></script> 47 <script src="/resources/testdriver-actions.js"></script> 48 <script src="/resources/testdriver-vendor.js"></script> 49 <!-- Additional helper script for common checks across event types --> 50 <script type="text/javascript" src="pointerevent_support.js"></script> 51 <script> 52 let frameLoaded = undefined; 53 const frameLoadedPromise = new Promise(resolve => { 54 frameLoaded = resolve; 55 }); 56 </script> 57 <body> 58 <div id="square1"></div> 59 <div> 60 <iframe onLoad = "frameLoaded()" id="innerFrame" srcdoc=' 61 <style> 62 html { 63 touch-action: none; 64 } 65 #square2 { 66 background-color: green; 67 border: 1px solid black; 68 height: 50px; 69 width: 50px; 70 display: inline-block; 71 } 72 </style> 73 <script> 74 // Prevent opening contextmenu. 75 addEventListener("contextmenu", e => e.preventDefault()); 76 </script> 77 <body> 78 <div id="square2"></div> 79 </body> 80 '></iframe> 81 </div> 82 <!-- Used to detect a sentinel event. Once triggered, all other events must 83 have been processed. --> 84 <div> 85 <button id="done">done</button> 86 </div> 87 </body> 88 <script> 89 window.onload = runTests(); 90 91 async function runTests() { 92 // Prevent opening contextmenu. 93 addEventListener("contextmenu", e => e.preventDefault()); 94 95 const queryStringFragments = location.search.substring(1).split('-'); 96 const pointerType = queryStringFragments[0]; 97 const button = queryStringFragments[1] === "right" ? "right" : undefined; 98 const standard = !(queryStringFragments[queryStringFragments.length - 1] === "nonstandard"); 99 100 const eventList = [ 101 'pointerover', 102 'pointerenter', 103 'pointerdown', 104 'pointerup', 105 'pointerout', 106 'pointerleave', 107 'pointermove' 108 ]; 109 110 function injectScrubGesture(element) { 111 const actions = new test_driver.Actions(); 112 113 let buttonArguments = 114 (button == 'right') ? { button: actions.ButtonType.RIGHT } 115 : undefined; 116 117 // The following comments refer to the first event of each type since 118 // that is what is being validated in the test. 119 return actions 120 .addPointer('pointer1', pointerType) 121 // The pointermove, pointerover and pointerenter events will be 122 // triggered here with a hover pointer. 123 .pointerMove(0, -20, { origin: element }) 124 // Pointerdown triggers pointerover, pointerenter with a non-hover 125 // pointer type. 126 .pointerDown(buttonArguments) 127 // This move triggers pointermove with a non-hover pointer-type. 128 .pointerMove(0, 20, { origin: element }) 129 // The pointerout and pointerleave events are triggered here with a 130 // touch pointer. 131 .pointerUp(buttonArguments) 132 // An addition move outside of the target bounds is required to trigger 133 // pointerout & pointerleave events with a hover pointer. 134 .pointerMove(0, 0) 135 .send(); 136 } 137 138 // Processing a click or tap on the done button is used to signal that all 139 // other events should have been handled. This is used to catch unhandled 140 // events that would otherwise result in a timeout. 141 function clickOrTapDone() { 142 const doneButton = document.getElementById('done'); 143 const pointerupPromise = getEvent('pointerup', doneButton); 144 const actionPromise = new test_driver.Actions() 145 .addPointer('pointer1', 'touch') 146 .pointerMove(0, 0, {origin: doneButton}) 147 .pointerDown() 148 .pointerUp() 149 .send(); 150 return actionPromise.then(pointerupPromise); 151 } 152 153 function verifyButtonAttributes(event) { 154 let downButton, upButton, downButtons, upButtons; 155 if (button == 'right') { 156 downButton = 2; 157 downButtons = 2; 158 upButton = 2; 159 upButtons = 0; 160 } else { 161 // defaults to left button click 162 downButton = 0; 163 downButtons = 1; 164 upButton = 0; 165 upButtons = 0; 166 } 167 const expectationsHover = { 168 // Pointer over, enter, and move are processed before the button press. 169 pointerover: { button: -1, buttons: 0 }, 170 pointerenter: { button: -1, buttons: 0 }, 171 pointermove: { button: -1, buttons: 0 }, 172 // Button status changes on pointer down and up. 173 pointerdown: { button: downButton, buttons: downButtons }, 174 pointerup: { button: upButton, buttons: upButtons }, 175 // Pointer out and leave are processed after the button release. 176 pointerout: { button: -1, buttons: 0 }, 177 pointerleave: { button: -1, buttons: 0 } 178 }; 179 const expectationsNoHover = { 180 // We don't see pointer events except during a touch gesture. 181 // Move is the only pointer event where the "button" click state is not 182 // changing. All other pointer events are associated with the start or 183 // end of a touch gesture. 184 pointerover: { button: 0, buttons: 1 }, 185 pointerenter: { button: 0, buttons: 1 }, 186 pointerdown: { button: 0, buttons: 1 }, 187 pointermove: { button: -1, buttons: 1 }, 188 pointerup: { button: 0, buttons: 0 }, 189 pointerout: { button: 0, buttons: 0 }, 190 pointerleave: { button: 0, buttons: 0 } 191 }; 192 const expectations = 193 (pointerType == 'touch') ? expectationsNoHover : expectationsHover; 194 195 assert_equals(event.button, expectations[event.type].button, 196 `Button attribute on ${event.type}`); 197 assert_equals(event.buttons, expectations[event.type].buttons, 198 `Buttons attribute on ${event.type}`); 199 } 200 201 function verifyPosition(event) { 202 const boundingRect = event.target.getBoundingClientRect(); 203 204 // With a touch pointer type, the pointerout and pointerleave will trigger 205 // on pointerup while clientX and clientY are still within the target's 206 // bounds. With a hover pointer, these events will be triggered only after 207 // clientX or clientY are out of the target's bounds. 208 if (pointerType != 'touch' && 209 (event.type == 'pointerout' || event.type == 'pointerleave')) { 210 assert_true( 211 boundingRect.left > event.clientX || 212 boundingRect.right < event.clientX || 213 boundingRect.top > event.clientY || 214 boundingRect.bottom < event.clientY, 215 `clientX/clientY is outside the element bounds for ${event.type} event`); 216 } else { 217 assert_true( 218 boundingRect.left <= event.clientX && 219 boundingRect.right >= event.clientX, 220 `clientX is within the expected range for ${event.type} event`); 221 assert_true( 222 boundingRect.top <= event.clientY && 223 boundingRect.bottom >= event.clientY, 224 `clientY is within the expected range for ${event.type} event`); 225 } 226 } 227 228 function verifyEventAttributes(event, testNamePrefix) { 229 verifyButtonAttributes(event); 230 verifyPosition(event); 231 assert_true(event.isPrimary, 'isPrimary attribute is true'); 232 check_PointerEvent(event, testNamePrefix, standard); 233 } 234 235 function pointerPromise(test, testNamePrefix, type, target) { 236 let rejectCallback = undefined; 237 promise = new Promise((resolve, reject) => { 238 // Store a reference to the promise rejection functions, which would 239 // otherwise not be visible outside the promise object. If the callback 240 // remains set when the deadline is reached, it means that the promise 241 // will not get resolved and should be rejected. 242 rejectCallback = reject; 243 const pointerEventListener = event => { 244 rejectCallback = undefined; 245 assert_equals(event.type, type, `type attribute for ${type} event`); 246 event.preventDefault(); 247 resolve(event); 248 }; 249 target.addEventListener(type, pointerEventListener, { once: true }); 250 test.add_cleanup(() => { 251 // Just in case of an assert prior to the events being triggered. 252 document.removeEventListener(type, pointerEventListener, 253 { once: true }); 254 }); 255 }).then(result => { verifyEventAttributes(result, testNamePrefix); }, 256 error => { assert_unreached(error); }); 257 promise.deadlineReached = () => { 258 // If the event has not been received, the promise will not be 259 // fulfilled, leading to a timeout. Reject the promise if still pending. 260 if (rejectCallback) { 261 rejectCallback(`missing ${type} event`); 262 } 263 } 264 return promise; 265 } 266 267 async function runPointerEventsTest(test, testNamePrefix, target) { 268 assert_true(['mouse', 'pen', 'touch'].indexOf(pointerType) >= 0, 269 `Unexpected pointer type (${pointerType})`); 270 271 const promises = []; 272 eventList.forEach(type => { 273 // Create a promise for each event type. If clicking on the done button 274 // is detected before an event's promise is resolved, then the promise 275 // will be rejected. Otherwise, the attributes for the event are 276 // verified. 277 promises.push(pointerPromise(test, testNamePrefix, type, target)); 278 }); 279 280 await injectScrubGesture(target); 281 282 // The injected gestures consist of a shrub on a button followed by a 283 // click on the done button. The promise is only resolved after the 284 // done click is detected. At this stage all other events must have been 285 // processed. Any unresolved promises in the list will be rejected to 286 // avoid a test timeout. The rejection will trigger a test failure. 287 await clickOrTapDone().then(promises.map(p => p.deadlineReached())); 288 289 // Once all promises are resolved, all event attributes have been 290 // successfully verified. 291 return Promise.all(promises); 292 } 293 294 promise_test(t => { 295 const square1 = document.getElementById('square1'); 296 return runPointerEventsTest(t, '', square1); 297 }, 'Test pointer events in the main document'); 298 299 promise_test(async t => { 300 const innerFrame = document.getElementById('innerFrame'); 301 await frameLoadedPromise; 302 const square2 = innerFrame.contentDocument.getElementById('square2'); 303 return runPointerEventsTest(t, 'Inner Frame', square2); 304 }, 'Test pointer events in an iframe'); 305 } 306 </script>