Source code
Revision control
Copy as Markdown
Other Tools
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Test for scrolled out of view animation optimization in an OOPIF</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="helper_fission_utils.js"></script>
<script src="apz_test_native_event_utils.js"></script>
<script src="apz_test_utils.js"></script>
</head>
<div style="width: 300px; height: 300px; overflow: hidden;" id="scroller">
<div style="width: 100%; height: 1000px;"></div>
<!-- I am not sure it's worth setting scrolling="no" and pointer-events: none. -->
<!-- I just want to make sure that HitTestingTreeNode is generated even with these properties. -->
<iframe scrolling="no" style="pointer-events: none;" id="testframe"></iframe>
</div>
<script>
async function setup_in_oopif() {
let iframeURL = SimpleTest.getTestFileURL("helper_fission_plain.html");
// We can't use `setupCrossOriginIFrame` directly here since the iframe here
// is clipped out by an overflow: hidden ancestor, thus any APZ stuff
// corresponding to the iframe document hasn't been established.
const iframe = document.querySelector("iframe");
const iframeLoadPromise = promiseOneEvent(iframe, "load", null);
iframe.src = iframeURL;
await iframeLoadPromise;
await SpecialPowers.spawn(iframe, [], async () => {
// Load utility functions for animation stuff.
const script = content.document.createElement("script");
script.setAttribute("src", "/tests/dom/animation/test/testcommon.js");
content.document.head.appendChild(script);
const extraStyle = content.document.createElement("style");
content.document.head.appendChild(extraStyle);
// an animation doesn't cause any geometric changes and doesn't run on the
// compositor either
extraStyle.sheet.insertRule("@keyframes anim { from { color: red; } to { color: blue; } }", 0);
const div = content.document.createElement("div");
// Position an element for animation at top: 20px.
div.style = "position: absolute; top: 40px; animation: anim 1s infinite; box-shadow: 0px -20px red;";
div.setAttribute("id", "target");
div.innerHTML = "hello";
content.document.body.appendChild(div);
await new Promise(resolve => {
script.onload = async () => {
// Force to flush the first style to avoid the first style is observed.
content.document.querySelector("#target").getAnimations()[0];
await content.wrappedJSObject.promiseFrame();
resolve();
};
});
});
}
async function observe_styling_in_oopif(aFrameCount) {
const iframe = document.querySelector("iframe");
return await SpecialPowers.spawn(iframe, [aFrameCount], async (frameCount) => {
// Start in a rAF callback.
await content.wrappedJSObject.waitForAnimationFrames(1);
return await content.wrappedJSObject.
observeStylingInTargetWindow(content.window, frameCount);
});
}
async function promiseScrollInfoArrivalInOOPIF() {
const scrollPromise = new Promise(resolve => {
scroller.addEventListener("scroll", resolve, { once: true });
});
const transformReceivedPromise = SpecialPowers.spawn(testframe, [], async () => {
await SpecialPowers.contentTransformsReceived(content);
});
await Promise.all([scrollPromise, transformReceivedPromise]);
}
function isWindows11() {
return getPlatform() == "windows" &&
SpecialPowers.Services.sysinfo.getProperty("version", null) == "10.0" &&
SpecialPowers.Services.sysinfo.getProperty("build", null) == "22621";
}
// The actual test
function assertThrottledRestyles(restyleCount, msg) {
// Allow 1 restyle count on Windows 11 since on our CIs there's something
// causing force flushing.
if (isWindows11()) {
ok(restyleCount <= 1, msg);
} else {
is(restyleCount, 0, msg);
}
}
async function test() {
// Generate an infinite animation which is initially clipped out by
// overflow: hidden style in the out-of-process iframe.
await setup_in_oopif();
let restyleCount = await observe_styling_in_oopif(5);
assertThrottledRestyles(
restyleCount,
"Animation in an out-of-process iframe which is initially clipped out " +
"due to 'overflow: hidden' should be throttled");
// Scroll synchronously to a position where the iframe gets visible.
scroller.scrollTo(0, 1000);
await promiseScrollInfoArrivalInOOPIF();
// Wait for a frame to make sure the notification of the last scroll position
// from APZC reaches the iframe process
await observe_styling_in_oopif(1);
restyleCount = await observe_styling_in_oopif(5);
is(restyleCount, 5,
"Animation in an out-of-process iframe which is no longer clipped out " +
"should NOT be throttled");
// Scroll synchronously to a position where the iframe is invisible again.
scroller.scrollTo(0, 0);
await promiseScrollInfoArrivalInOOPIF();
// Wait for a frame to make sure the notification of the last scroll position
// from APZC reaches the iframe process
await observe_styling_in_oopif(1);
restyleCount = await observe_styling_in_oopif(5);
assertThrottledRestyles(
restyleCount,
"Animation in an out-of-process iframe which is clipped out again " +
"should be throttled again");
// ===== Asyncronous scrolling tests =====
scroller.style.overflow = "scroll";
// Scroll asynchronously to a position where the animating element gets
// visible.
scroller.scrollTo({ left: 0, top: 750, behavior: "smooth"});
// Wait for the asyncronous scroll finish. `60` frames is the same number in
// helper_fission_scroll_oopif.html
await observe_styling_in_oopif(60);
restyleCount = await observe_styling_in_oopif(5);
is(restyleCount, 5,
"Animation in an out-of-process iframe which is now visible by " +
"asynchronous scrolling should NOT be throttled");
// Scroll asynchronously to a position where the iframe is still visible but
// the animating element gets invisible.
scroller.scrollTo({ left: 0, top: 720, behavior: "smooth"});
// Wait for the asyncronous scroll finish.
await observe_styling_in_oopif(60);
restyleCount = await observe_styling_in_oopif(5);
assertThrottledRestyles(
restyleCount,
"Animation in an out-of-process iframe which is scrolled out of view by " +
"asynchronous scrolling should be throttled");
// Scroll asynchronously to a position where the animating element gets
// visible again.
scroller.scrollTo({ left: 0, top: 750, behavior: "smooth"});
// Wait for the asyncronous scroll finish.
await observe_styling_in_oopif(60);
restyleCount = await observe_styling_in_oopif(5);
is(restyleCount, 5,
"Animation in an out-of-process iframe appeared by the asynchronous " +
"scrolling should be NOT throttled");
}
waitUntilApzStable()
.then(test)
.then(subtestDone, subtestFailed);
</script>
</html>