helper_hittest_overscroll.html (8655B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <title>Test APZ hit-testing while overscrolled</title> 5 <script type="application/javascript" src="apz_test_utils.js"></script> 6 <script type="application/javascript" src="apz_test_native_event_utils.js"></script> 7 <script src="/tests/SimpleTest/paint_listener.js"></script> 8 <meta name="viewport" content="width=device-width"/> 9 <style> 10 html, body { 11 margin: 0; 12 padding: 0; 13 } 14 .spacer { 15 height: 5000px; 16 } 17 #target { 18 margin-left: 100px; 19 margin-top: 2px; 20 height: 4px; 21 width: 4px; 22 background: red; 23 } 24 #fixedtarget { 25 left: 300px; 26 height: 20px; 27 width: 5px; 28 top: 2px; 29 background: green; 30 position: fixed; 31 } 32 #fixedtargetbutton { 33 height: 10px; 34 width: 5px; 35 padding: 0; 36 margin-top: 10px; 37 margin-left: 0; 38 border: 0; 39 } 40 </style> 41 </head> 42 <body> 43 <div id="target"></div> 44 <div id="fixedtarget"> 45 <div id="fixedtargetbutton"></div> 46 </div> 47 <div id="spacer" class="spacer"></div> 48 </body> 49 <script type="application/javascript"> 50 51 // Some helper functions for listening for click events in the browser chrome. 52 53 // A handle used to interact with the chrome script used to implement 54 // [start|stop]ListeningFOrClickEventsInChrome(). 55 let chromeScriptHandle = null; 56 57 function startListeningForClickEventsInChrome() { 58 function chromeScript() { 59 /* eslint-env mozilla/chrome-script */ 60 let topWin = Services.wm.getMostRecentWindow("navigator:browser"); 61 if (!topWin) { 62 topWin = Services.wm.getMostRecentWindow("navigator:geckoview"); 63 } 64 let chromeReceivedClick = false; 65 function chromeListener() { 66 chromeReceivedClick = true; 67 } 68 topWin.addEventListener("click", chromeListener); 69 function queryClicked() { 70 sendAsyncMessage("query-clicked-response", { chromeReceivedClick }); 71 } 72 function cleanup() { 73 topWin.removeEventListener("click", chromeListener); 74 removeMessageListener("query-clicked", queryClicked); 75 removeMessageListener("cleanup", cleanup); 76 } 77 addMessageListener("query-clicked", queryClicked); 78 addMessageListener("cleanup", cleanup); 79 } 80 chromeScriptHandle = SpecialPowers.loadChromeScript(chromeScript); 81 } 82 83 async function didChromeReceiveClick() { 84 chromeScriptHandle.sendAsyncMessage("query-clicked", null); 85 let response = await chromeScriptHandle.promiseOneMessage("query-clicked-response"); 86 ok(response && ("chromeReceivedClick" in response), 87 "Received a well-formed response from chrome script"); 88 return response.chromeReceivedClick; 89 } 90 91 function stopListeningForClickEventsInChrome() { 92 chromeScriptHandle.sendAsyncMessage("cleanup", null); 93 chromeScriptHandle.destroy(); 94 } 95 96 async function test() { 97 var config = getHitTestConfig(); 98 var utils = config.utils; 99 100 // Overscroll the root scroll frame at the top, creating a gutter. 101 // Note that the size of the gutter will only be 8px, because 102 // setAsyncScrollOffset() applies the overscroll as a single delta, 103 // and current APZ logic that transforms a delta into an overscroll 104 // amount limits each delta to at most 8px. 105 utils.setAsyncScrollOffset(document.documentElement, 0, -200); 106 107 // Check that the event hits the root scroll frame in APZ. 108 // This is important because additional pan-gesture events in the gutter 109 // should continue to be handled and cause further overscroll (or 110 // relieving overscroll, depending on their direction). 111 let hitResult = hitTest({x: 100, y: 4}); 112 let rootViewId = utils.getViewId(document.documentElement); 113 checkHitResult(hitResult, 114 APZHitResultFlags.VISIBLE, 115 rootViewId, 116 utils.getLayersId(), 117 "APZ hit-test in the root gutter"); 118 119 // Now, perform a click in the gutter and check that APZ prevents 120 // the event from reaching Gecko. 121 // To be sure that no event was dispatched to Gecko, install listeners 122 // on both the browser chrome window and the content window. 123 // This makes sure we catch the case where the overscroll transform causes 124 // the event to incorrectly target the browser chrome. 125 126 //// Util function to perform mouse events in the gutter. Used to ensure these 127 //// events are not dispatched to the content. 128 async function clickInGutter(xOffset, yOffset) { 129 startListeningForClickEventsInChrome(); 130 let contentReceivedClick = false; 131 let contentListener = function(e) { 132 info("event clientX = " + e.clientX); 133 info("event clientY = " + e.clientY); 134 info("event target id: " + e.target.id); 135 contentReceivedClick = true; 136 }; 137 document.addEventListener("click", contentListener); 138 await synthesizeNativeMouseEventWithAPZ({ 139 type: "click", 140 target: window, 141 offsetX: xOffset, 142 offsetY: yOffset, 143 }); 144 // Wait 10 frames for the event to maybe arrive, and if it 145 // hasn't, assume it won't. 146 for (let i = 0; i < 10; i++) { 147 await promiseFrame(); 148 } 149 info("Finished waiting around for click event"); 150 let chromeReceivedClick = await didChromeReceiveClick(); 151 ok(!chromeReceivedClick, 152 "Gecko received click event in browser chrome when it shouldn't have"); 153 ok(!contentReceivedClick, 154 "Gecko received click event targeting web content when it shouldn't have"); 155 stopListeningForClickEventsInChrome(); 156 document.removeEventListener("click", contentListener); 157 } 158 159 // Perform the test 160 await clickInGutter(100, 4); 161 162 // Finally, while still overscrolled, perform a click not in the gutter. 163 // This event should successfully go through to the web content, and 164 // be untransformed by the overscroll transform (such that it hits the 165 // content that is visually under the cursor). 166 let clickPromise = new Promise(resolve => { 167 document.addEventListener("click", function(e) { 168 info("event clientX = " + e.clientX); 169 info("event clientY = " + e.clientY); 170 is(e.target, target, "Click while overscrolled hit intended target"); 171 resolve(); 172 }, { once: true }); 173 }); 174 await synthesizeNativeMouseEventWithAPZ({ 175 type: "click", 176 target: window, 177 offsetX: 102, 178 offsetY: 12, 179 }); 180 await clickPromise; 181 182 // Test that mouse events targetting the fixed content are dispatched 183 // to the content. 184 async function clickFixed(yOffset, expectedTarget) { 185 let clickFixedPromise = new Promise(resolve => { 186 document.addEventListener("click", function(e) { 187 info("event clientX = " + e.clientX); 188 info("event clientY = " + e.clientY); 189 info("event target id: " + e.target.id); 190 is(e.target, expectedTarget, "Click while overscrolled hit intended target"); 191 resolve(); 192 }, { once: true }); 193 }); 194 await synthesizeNativeMouseEventWithAPZ({ 195 type: "click", 196 target: window, 197 offsetX: 302, 198 offsetY: yOffset, 199 }); 200 await clickFixedPromise; 201 } 202 203 // This hit is technically in the gutter created by the overscroll, but we 204 // should still dispatch to gecko due to the fixed element extending into 205 // the gutter. 206 await clickFixed(4, fixedtarget, false); 207 // Perform various mouse events to ensure the fixed element has not moved 208 await clickFixed(10, fixedtarget, false); 209 await clickFixed(14, fixedtargetbutton, false); 210 await clickFixed(18, fixedtargetbutton, false); 211 212 let clickFixedPromise = new Promise(resolve => { 213 document.addEventListener("click", function(e) { 214 info("event clientX = " + e.clientX); 215 info("event clientY = " + e.clientY); 216 info("event target id: " + e.target.id); 217 // TODO(dlrobertson): What exists directly below the fixed element? 218 // Should there be a gutter below the fixed element? Or should events 219 // directed below the fixed element be dispatched normally. In the 220 // transform of the mouse event, the hit will not have any side bits 221 // set and we will transform the hit result. As a result, 25 will be 222 // transformed to ~17, and the event will be dispatched to the fixed 223 // element. 224 todo(false, 225 "Click while overscrolled hit intended target below fixed content"); 226 resolve(); 227 }, { once: true }); 228 }); 229 await synthesizeNativeMouseEventWithAPZ({ 230 type: "click", 231 target: window, 232 offsetX: 302, 233 offsetY: 25, 234 }); 235 await clickFixedPromise; 236 237 // Click above the fixed element, but in the gutter. 238 await clickInGutter(302, 1); 239 // Click left of the the fixed element, but in the gutter. 240 await clickInGutter(298, 4); 241 } 242 243 waitUntilApzStable() 244 .then(test) 245 .then(subtestDone, subtestFailed); 246 247 </script> 248 </html>