helper_mainthread_scroll_bug1662379.html (5394B)
1 <!DOCTYPE html> 2 <meta charset="utf-8"> 3 <meta name="viewport" content="width=device-width, minimum-scale=1.0"> 4 <script src="apz_test_utils.js"></script> 5 <script src="apz_test_native_event_utils.js"></script> 6 <script src="/tests/SimpleTest/paint_listener.js"></script> 7 <div id="content"> 8 <div id="lhs"> 9 <ul> 10 <li>Test item 1</li> 11 <li>Test item 2</li> 12 <li>Test item 3</li> 13 <li>Test item 4</li> 14 <li>Test item 5</li> 15 <li>Test item 6</li> 16 <li>Test item 7</li> 17 <li>Test item 8</li> 18 <li>Test item 9</li> 19 <li>Test item 10</li> 20 <li>Test item 11</li> 21 <li>Test item 13</li> 22 <li>Test item 14</li> 23 <li>Test item 15</li> 24 <li>Test item 16</li> 25 <li>Test item 17</li> 26 <li>Test item 18</li> 27 <li>Test item 19</li> 28 </ul> 29 </div> 30 <div id="center"> 31 <div> 32 Steps to reproduce: 33 <ol> 34 <li>Scroll the list of "test items" all the way to the bottom 35 <li>Click on the reparent button below 36 <li>Click on one of the test items 37 <li>The `clickTarget` JS variable should match the thing you clicked on 38 </ol> 39 </div> 40 <button onclick="reparent()"> Click here to reparent </button> 41 </div> 42 </div> 43 <style> 44 #content { 45 display: flex; 46 height: 300px; 47 background-color: pink; 48 border: 3px solid green; 49 } 50 51 #lhs, #rhs { 52 width: 250px; 53 overflow: scroll; 54 flex: 0 0 250px 55 } 56 57 #center { 58 padding: 20px; 59 } 60 61 ul { 62 margin: 16px 0px; 63 } 64 65 /* Each element has a border-height of 20 + (2 * 5) + (2 * 1) = 32px */ 66 ul li { 67 background-color: aqua; 68 border: 1px solid blue; 69 padding: 5px; 70 cursor: pointer; 71 height: 20px; 72 } 73 </style> 74 <script> 75 var clickTarget = null; 76 77 for (var el of document.querySelectorAll("ul li")) { 78 el.addEventListener("click", function(e) { 79 clickTarget = e.target; 80 }); 81 } 82 83 function reparent() { 84 var content = document.getElementById("content"); 85 var lhs = document.getElementById("lhs"); 86 content.appendChild(lhs); 87 lhs.id = "rhs"; 88 } 89 90 function getAsyncScrollOffsetForUniqueRcdChild() { 91 let apzcTree = getLastApzcTree(); 92 let rcd = findRcdNode(apzcTree); 93 // We assume a unique child of the RCD. If this is not the case, bail out. 94 if (rcd == null || rcd.children.length != 1) { 95 info("Did not find unique child on the RCD: rcd=" + JSON.stringify(rcd)); 96 return {x: -1, y: -1}; 97 } 98 let child = rcd.children[0]; 99 return parsePoint(child.asyncScrollOffset); 100 } 101 102 async function test() { 103 if (getPlatform() == "android") { 104 ok(true, "Mousewheel is not supported on android, skipping test..."); 105 return; 106 } 107 108 // Simulate user mouse-wheel scrolling the lhs pane down to the bottom. 109 let lhs = document.getElementById("lhs"); 110 let scrollendPromise = promiseScrollend(lhs); 111 while (lhs.scrollTop < lhs.scrollTopMax) { 112 await promiseNativeWheelAndWaitForScrollEvent( 113 lhs, 114 50, 50, 115 0, -50); 116 info("Did scroll, pos is now " + lhs.scrollTop + "/" + lhs.scrollTopMax); 117 } 118 // Wait for the animation to be completely done. (scrollTop is rounded to 119 // the nearest integer value, so at the time scrollTop reaches scrollTopMax, 120 // the compositor animation may still be a sub-pixel amount away from the 121 // destination). 122 await scrollendPromise; 123 await promiseApzFlushedRepaints(); 124 125 // Click at 50,50 from the top-left corner of the lhs pane. If lhs were 126 // not scrolled, this would hit "Test item 2" but since lhs is scrolled 127 // it should hit something else. So let's check that it doesn't hit 128 // "Test item 2". 129 await promiseNativeMouseEventWithAPZAndWaitForEvent({ 130 type: "click", 131 target: lhs, 132 offsetX: 50, 133 offsetY: 50, 134 }); 135 isnot(clickTarget, null, "Click target got set"); 136 info("Hit " + clickTarget.textContent); 137 isnot(clickTarget.textContent, "Test item 2", "Item two didn't get hit"); 138 clickTarget = null; 139 140 // Do the reparenting 141 reparent(); 142 await promiseApzFlushedRepaints(); 143 info("Done reparent + wait, about to fire click..."); 144 145 // Now fire the click on the reparented element (which is now called "rhs") 146 // at the same 50,50 offset from the top-left. This time it *should* hit 147 // "Test item 2" because the reparenting should reset the scroll offset 148 // back to zero. 149 await promiseNativeMouseEventWithAPZAndWaitForEvent({ 150 type: "click", 151 target: document.getElementById("rhs"), 152 offsetX: 50, 153 offsetY: 50, 154 }); 155 156 // Check that the visual scroll position (as determined by the compositor 157 // scroll offset) is consistent with the main-thread scroll position (as 158 // determined by the click target). In both cases the scroll position 159 // should have gotten reset back to zero with the reparenting step. In 160 // bug 1662379 the visual scroll position was *not* getting reset, and 161 // so even though the main-thread click target was "Test item 2", the 162 // compositor scroll offset was non-zero, so to the user the click 163 // appeared to trigger something different from what they clicked on. 164 isnot(clickTarget, null, "Click target got set"); 165 is(clickTarget.textContent, "Test item 2", "Item two got hit correctly"); 166 let rhsCompositorScrollOffset = getAsyncScrollOffsetForUniqueRcdChild(); 167 is(rhsCompositorScrollOffset.x, 0, "rhs compositor x-offset is zero"); 168 is(rhsCompositorScrollOffset.y, 0, "rhs compositor y-offset is zero"); 169 } 170 171 waitUntilApzStable() 172 .then(test) 173 .then(subtestDone, subtestFailed); 174 </script>