helper_fission_animation_styling_in_oopif.html (7128B)
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 scrolled out of view animation optimization in an OOPIF</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_native_event_utils.js"></script> 11 <script src="apz_test_utils.js"></script> 12 </head> 13 <div style="width: 300px; height: 300px; overflow: hidden;" id="scroller"> 14 <div style="width: 100%; height: 1000px;"></div> 15 <!-- I am not sure it's worth setting scrolling="no" and pointer-events: none. --> 16 <!-- I just want to make sure that HitTestingTreeNode is generated even with these properties. --> 17 <iframe scrolling="no" style="pointer-events: none;" id="testframe"></iframe> 18 </div> 19 <script> 20 async function setup_in_oopif() { 21 let iframeURL = SimpleTest.getTestFileURL("helper_fission_plain.html"); 22 iframeURL = iframeURL.replace(window.location.origin, "https://example.com"); 23 24 // We can't use `setupCrossOriginIFrame` directly here since the iframe here 25 // is clipped out by an overflow: hidden ancestor, thus any APZ stuff 26 // corresponding to the iframe document hasn't been established. 27 const iframe = document.querySelector("iframe"); 28 const iframeLoadPromise = promiseOneEvent(iframe, "load", null); 29 iframe.src = iframeURL; 30 await iframeLoadPromise; 31 32 await SpecialPowers.spawn(iframe, [], async () => { 33 // Load utility functions for animation stuff. 34 const script = content.document.createElement("script"); 35 script.setAttribute("src", "/tests/dom/animation/test/testcommon.js"); 36 content.document.head.appendChild(script); 37 38 const extraStyle = content.document.createElement("style"); 39 content.document.head.appendChild(extraStyle); 40 // an animation doesn't cause any geometric changes and doesn't run on the 41 // compositor either 42 extraStyle.sheet.insertRule("@keyframes anim { from { color: red; } to { color: blue; } }", 0); 43 44 const div = content.document.createElement("div"); 45 // Position an element for animation at top: 20px. 46 div.style = "position: absolute; top: 40px; animation: anim 1s infinite; box-shadow: 0px -20px red;"; 47 div.setAttribute("id", "target"); 48 div.innerHTML = "hello"; 49 content.document.body.appendChild(div); 50 await new Promise(resolve => { 51 script.onload = async () => { 52 // Force to flush the first style to avoid the first style is observed. 53 content.document.querySelector("#target").getAnimations()[0]; 54 // FIXME: Bug 1578309 use anim.ready instead. 55 await content.wrappedJSObject.promiseFrame(); 56 resolve(); 57 }; 58 }); 59 }); 60 } 61 62 async function observe_styling_in_oopif(aFrameCount) { 63 const iframe = document.querySelector("iframe"); 64 return await SpecialPowers.spawn(iframe, [aFrameCount], async (frameCount) => { 65 // Start in a rAF callback. 66 await content.wrappedJSObject.waitForAnimationFrames(1); 67 68 return await content.wrappedJSObject. 69 observeStylingInTargetWindow(content.window, frameCount); 70 }); 71 } 72 73 async function promiseScrollInfoArrivalInOOPIF() { 74 const scrollPromise = new Promise(resolve => { 75 scroller.addEventListener("scroll", resolve, { once: true }); 76 }); 77 78 const transformReceivedPromise = SpecialPowers.spawn(testframe, [], async () => { 79 await SpecialPowers.contentTransformsReceived(content); 80 }); 81 82 await Promise.all([scrollPromise, transformReceivedPromise]); 83 } 84 85 function isWindows11() { 86 return getPlatform() == "windows" && 87 SpecialPowers.Services.sysinfo.getProperty("version", null) == "10.0" && 88 SpecialPowers.Services.sysinfo.getProperty("build", null) == "22621"; 89 } 90 91 // The actual test 92 93 function assertThrottledRestyles(restyleCount, msg) { 94 // Allow 1 restyle count on Windows 11 since on our CIs there's something 95 // causing force flushing. 96 if (isWindows11()) { 97 ok(restyleCount <= 1, msg); 98 } else { 99 is(restyleCount, 0, msg); 100 } 101 } 102 103 async function test() { 104 // Generate an infinite animation which is initially clipped out by 105 // overflow: hidden style in the out-of-process iframe. 106 await setup_in_oopif(); 107 108 let restyleCount = await observe_styling_in_oopif(5); 109 assertThrottledRestyles( 110 restyleCount, 111 "Animation in an out-of-process iframe which is initially clipped out " + 112 "due to 'overflow: hidden' should be throttled"); 113 114 // Scroll synchronously to a position where the iframe gets visible. 115 scroller.scrollTo(0, 1000); 116 await promiseScrollInfoArrivalInOOPIF(); 117 118 // Wait for a frame to make sure the notification of the last scroll position 119 // from APZC reaches the iframe process 120 await observe_styling_in_oopif(1); 121 122 restyleCount = await observe_styling_in_oopif(5); 123 is(restyleCount, 5, 124 "Animation in an out-of-process iframe which is no longer clipped out " + 125 "should NOT be throttled"); 126 127 // Scroll synchronously to a position where the iframe is invisible again. 128 scroller.scrollTo(0, 0); 129 await promiseScrollInfoArrivalInOOPIF(); 130 131 // Wait for a frame to make sure the notification of the last scroll position 132 // from APZC reaches the iframe process 133 await observe_styling_in_oopif(1); 134 135 restyleCount = await observe_styling_in_oopif(5); 136 assertThrottledRestyles( 137 restyleCount, 138 "Animation in an out-of-process iframe which is clipped out again " + 139 "should be throttled again"); 140 141 // ===== Asyncronous scrolling tests ===== 142 scroller.style.overflow = "scroll"; 143 // Scroll asynchronously to a position where the animating element gets 144 // visible. 145 scroller.scrollTo({ left: 0, top: 750, behavior: "smooth"}); 146 147 // Wait for the asyncronous scroll finish. `60` frames is the same number in 148 // helper_fission_scroll_oopif.html 149 await observe_styling_in_oopif(60); 150 151 restyleCount = await observe_styling_in_oopif(5); 152 is(restyleCount, 5, 153 "Animation in an out-of-process iframe which is now visible by " + 154 "asynchronous scrolling should NOT be throttled"); 155 156 // Scroll asynchronously to a position where the iframe is still visible but 157 // the animating element gets invisible. 158 scroller.scrollTo({ left: 0, top: 720, behavior: "smooth"}); 159 160 // Wait for the asyncronous scroll finish. 161 await observe_styling_in_oopif(60); 162 163 restyleCount = await observe_styling_in_oopif(5); 164 assertThrottledRestyles( 165 restyleCount, 166 "Animation in an out-of-process iframe which is scrolled out of view by " + 167 "asynchronous scrolling should be throttled"); 168 169 // Scroll asynchronously to a position where the animating element gets 170 // visible again. 171 scroller.scrollTo({ left: 0, top: 750, behavior: "smooth"}); 172 173 // Wait for the asyncronous scroll finish. 174 await observe_styling_in_oopif(60); 175 176 restyleCount = await observe_styling_in_oopif(5); 177 is(restyleCount, 5, 178 "Animation in an out-of-process iframe appeared by the asynchronous " + 179 "scrolling should be NOT throttled"); 180 } 181 182 waitUntilApzStable() 183 .then(test) 184 .then(subtestDone, subtestFailed); 185 </script> 186 </html>