helper_fission_scroll_oopif.html (7734B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width,initial-scale=1"> 6 <title>Test for async-scrolling an OOPIF and ensuring hit-testing still works</title> 7 <script src="/tests/SimpleTest/SimpleTest.js"></script> 8 <script src="/tests/SimpleTest/paint_listener.js"></script> 9 <script src="helper_fission_utils.js"></script> 10 <script src="apz_test_utils.js"></script> 11 <script src="apz_test_native_event_utils.js"></script> 12 <script> 13 const { AppConstants } = SpecialPowers.ChromeUtils.importESModule( 14 "resource://gre/modules/AppConstants.sys.mjs" 15 ); 16 17 async function getWindowProtocol() { 18 return await SpecialPowers.spawnChrome([], () => { 19 try { 20 return Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo) 21 .windowProtocol; 22 } catch { 23 return ""; 24 } 25 }); 26 } 27 28 function getEventPromise(eventName) { 29 let eventPromise = new Promise(resolve => { 30 const listener = event => { 31 if (event.data === eventName) { 32 window.removeEventListener("message", listener); 33 resolve(); 34 } 35 } 36 window.addEventListener("message", listener); 37 }); 38 return eventPromise; 39 } 40 41 function getClickPromise() { 42 let clickPromise = new Promise(resolve => { 43 window.addEventListener("message", event => { 44 let data = JSON.parse(event.data); 45 if ("type" in data && data.type == "clicked") { 46 let coordinates = { 47 offsetX: data.coords.offsetX, 48 offsetY: data.coords.offsetY 49 }; 50 resolve(coordinates); 51 } 52 }, { once: true }); 53 }); 54 return clickPromise; 55 } 56 57 function getScrollYPromise() { 58 let scrollPromise = new Promise(resolve => { 59 window.addEventListener("message", event => { 60 let eventData = JSON.parse(event.data); 61 if ("type" in eventData && eventData.type == "scrolled") { 62 resolve(eventData.scrollY); 63 } 64 }, { once: true }); 65 }); 66 return scrollPromise; 67 } 68 69 async function clickOnIframe(x, y) { 70 let clickPromise = getClickPromise(); 71 await synthesizeNativeMouseEventWithAPZ( 72 { type: "click", target: document.body, offsetX: x, offsetY: y }, 73 () => dump("Finished synthesizing click, waiting for OOPIF message...\n") 74 ); 75 return clickPromise; 76 } 77 78 async function makeIframeScrollable(iframe) { 79 let parentScrollListenerReady = getEventPromise("scrollableReady"); 80 let iframeScrollMaxY = await SpecialPowers.spawn(iframe, [], async () => { 81 // Ensure the oopif is scrollable, and wait for the paint so that the 82 // compositor also knows it's scrollable. 83 content.document.body.style.height = "200vh"; 84 await content.wrappedJSObject.promiseApzFlushedRepaints(); 85 let scrollMaxY = content.window.scrollMaxY; 86 87 // Also register a scroll listener for when it actually gets scrolled. 88 content.window.addEventListener("scroll", function () { 89 dump(`OOPIF got scroll event, now at ${content.window.scrollY}\n`); 90 let data = JSON.stringify({ 91 type: "scrolled", 92 scrollY: content.window.scrollY 93 }); 94 content.window.parent.postMessage(data, "*"); 95 }, { once: true }); 96 content.window.parent.postMessage("scrollableReady", "*"); 97 return scrollMaxY; 98 }); 99 await parentScrollListenerReady; 100 return iframeScrollMaxY; 101 } 102 103 async function registerIframeClickListener(iframe) { 104 let readyPromise = getEventPromise("listenerReady"); 105 let iframeClickReady = await SpecialPowers.spawn(iframe, [], async () => { 106 content.document.addEventListener("click", (e) => { 107 let data = JSON.stringify({ 108 type: "clicked", 109 coords: { 110 offsetX: e.clientX, 111 offsetY: e.clientY 112 } 113 }); 114 content.window.parent.postMessage(data, "*"); 115 }); 116 content.window.parent.postMessage("listenerReady", "*"); 117 return true; 118 }); 119 await readyPromise; 120 return iframeClickReady; 121 } 122 123 /*------------------------------------------------------------------------*/ 124 125 async function test() { 126 // Skip on Wayland 127 const protocol = await getWindowProtocol(); 128 if (AppConstants.platform === "linux" && protocol === "wayland") { 129 ok(true, "Skipping this test on Linux with Wayland"); 130 return; 131 } 132 133 ok(SpecialPowers.getBoolPref("apz.paint_skipping.enabled"), 134 "Paint-skipping is expected to be enabled for this test to be meaningful"); 135 136 // Set up iframe 137 let iframe = document.getElementById("testframe"); 138 await setupCrossOriginIFrame(iframe, "helper_fission_plain.html"); 139 140 // Register click listener in iframe 141 let iframeClickReady = await registerIframeClickListener(iframe); 142 ok(iframeClickReady, "Successfully installed click listener in OOP iframe"); 143 144 // hit-test into the iframe before scrolling 145 let oldClickPoint = await clickOnIframe(50, 250); 146 147 // Ensure parent window is at 0 scroll position before scrolling 148 is(window.scrollY, 0, "window is at 0 scroll position"); 149 // do an APZ scroll and wait for the main-thread to get the repaint request, 150 // and queue up a paint-skip scroll notification back to APZ. 151 let scrollend = promiseScrollend(window); 152 await promiseMoveMouseAndScrollWheelOver(document.body, 10, 10); 153 await scrollend; 154 await promiseOnlyApzControllerFlushed(); 155 await promiseAllPaintsDone(); 156 ok(window.scrollY > 5, "window has scrolled by " + window.scrollY + " pixels"); 157 158 // hit-test into the iframe after scrolling. The coordinates here are the 159 // same relative to the body as before, but get computed to be different 160 // relative to the window/screen. 161 let newClickPoint = await clickOnIframe(50, 250); 162 is(newClickPoint.x, oldClickPoint.x, "x-coord of old and new match"); 163 is(newClickPoint.y, oldClickPoint.y, "y-coord of old and new match"); 164 165 // Also check that we can send scroll events to the OOPIF. Any wheel events 166 // delivered to this page after this point should result in a failure. 167 addEventListener("wheel", function () { 168 ok(false, "Got unwanted wheel event in parent document"); 169 }); 170 171 // Ensure iframe is at 0 scroll position 172 const iframeY = await SpecialPowers.spawn(iframe, [], () => { 173 return content.window.scrollY; 174 }); 175 is(iframeY, 0, "scrollY of iframe should be 0 initially"); 176 177 // Ensure the OOPIF is scrollable. 178 let iframeScrollMaxY = await makeIframeScrollable(iframe); 179 ok(iframeScrollMaxY > 0, "iframe successfully made scrollable"); 180 181 // Now scroll over the OOP-iframe (we know it must be under the 50,250 point 182 // because we just checked that above). Note that listening for wheel/scroll 183 // events is trickier because they will fire in the OOPIF, so we can't just 184 // use promiseMoveMouseAndScrollWheelOver directly. 185 let scrolledPromise = getScrollYPromise(); 186 await synthesizeNativeWheel(document.body, 50, 250, 0, -10); 187 let iframeScrollY = await scrolledPromise; 188 ok(iframeScrollY > 0, "Confirmed that oopif is scrollable"); 189 } 190 191 waitUntilApzStable() 192 .then(test) 193 .then(subtestDone, subtestFailed); 194 195 </script> 196 </head> 197 <body> 198 <iframe style="margin-top: 200px" id="testframe"></iframe> 199 <div style="height: 5000px">tall div to make the page scrollable</div> 200 </body> 201 </html>