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 css-animations running on the compositor thread with scroll-timeline</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/paint_listener.js"></script>
<script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script>
<script type="application/javascript" src="animation_utils.js"></script>
<style type="text/css">
@keyframes transform_anim {
from { transform: translate(50px); }
to { transform: translate(150px); }
}
@keyframes always_fifty {
from, to { transform: translate(50px); }
}
@keyframes geometry {
from { width: 50px; }
to { width: 100px; }
}
.target {
/* The animation target needs geometry in order to qualify for OMTA */
width: 100px;
height: 100px;
background-color: green;
}
.scroller {
width: 100px;
height: 100px;
overflow: scroll;
scroll-timeline-name: --scroll_timeline;
}
.content {
block-size: 100%;
padding-block-end: 100px;
}
</style>
</head>
<body>
<div id="display"></div>
<pre id="test"></pre>
</body>
<script type="application/javascript">
"use strict";
// Global state
var gScroller = null;
var gDiv = null;
// Shortcut omta_is and friends by filling in the initial 'elem' argument
// with gDiv.
[ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) {
var origFn = window[fn];
window[fn] = function() {
var args = Array.from(arguments);
if (!(args[0] instanceof Element)) {
args.unshift(gDiv);
}
return origFn.apply(window, args);
};
});
// Shortcut new_div and done_div to update gDiv
var originalNewDiv = window.new_div;
window.new_div = function(style) {
[ gDiv ] = originalNewDiv(style);
};
var originalDoneDiv = window.done_div;
window.done_div = function() {
originalDoneDiv();
gDiv = null;
};
// Bind the ok and todo to the opener, and close this window when we finish.
var ok = opener.ok.bind(opener);
var todo = opener.todo.bind(opener);
function finish() {
var o = opener;
self.close();
o.SimpleTest.finish();
}
function new_scroller() {
gScroller = document.createElement('div');
gScroller.className = `scroller`;
let content = document.createElement('div');
content.className = 'content';
gScroller.appendChild(content);
document.getElementById("display").appendChild(gScroller);
return gScroller;
}
function done_scroller() {
gScroller.firstChild.remove();
gScroller.remove();
gScroller = null;
}
waitUntilApzStable().then(() => {
runOMTATest(function() {
var onAbort = function() {
if (gDiv) {
done_div();
}
if (gScroller) {
done_scroller();
}
};
runAllAsyncAnimTests(onAbort).then(finish);
}, finish);
});
//----------------------------------------------------------------------
//
// Test cases
//
//----------------------------------------------------------------------
// The non-omta property with scroll-timeline together with an omta property
// with document-timeline.
addAsyncAnimTest(async function() {
new_scroller();
new_div("animation: geometry 10s, always_fifty 1s infinite; " +
"animation-timeline: --scroll_timeline, auto");
await waitForPaintsFlushed();
// Note: width is not a OMTA property, so it must be running on the main
// thread.
omta_is("transform", { tx: 50 }, RunningOn.Compositor,
"transform animations should runs on compositor thread");
done_div();
done_scroller();
});
// transform property with scroll-driven animations.
addAsyncAnimTest(async function() {
let scroller = new_scroller();
new_div("animation: transform_anim 1s linear; " +
"animation-timeline: --scroll_timeline;");
await waitForPaintsFlushed();
scroller.scrollTop = 50;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor,
"scroll transform animations should runs on compositor " +
"thread");
done_div();
done_scroller();
});
// The scroll-driven animation with an underlying value and make it go from the
// active phase to the before phase.
addAsyncAnimTest(async function() {
let scroller = new_scroller();
new_div("animation: always_fifty 5s linear 5s; " +
"animation-timeline: --scroll_timeline; " +
"transform: translate(25px);");
await waitForPaintsFlushed();
// NOTE: getOMTAStyle() can't detect the animation is running on the
// compositor during the delay phase.
omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either,
"The scroll animation is in delay");
scroller.scrollTop = 75;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
"scroll transform animations should runs on compositor " +
"thread");
// Use setAsyncScrollOffset() to update apz (compositor thread only) to make
// sure Bug 1776077 is reproducible.
let utils = SpecialPowers.wrap(window).windowUtils;
utils.setAsyncScrollOffset(scroller, 0, -50);
utils.advanceTimeAndRefresh(16);
utils.restoreNormalRefresh();
await waitForPaints();
// NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
// OMTA style directly.
let compositorStr =
SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
ok(compositorStr === "matrix(1, 0, 0, 1, 25, 0)",
"scroll animations is in delay phase before calling main thread style " +
"udpate");
scroller.scrollTop = 25;
await waitForPaintsFlushed();
compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
ok(compositorStr === "",
"scroll animation in delay phase clears its OMTA style");
omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either,
"The scroll animation is in delay");
done_div();
done_scroller();
});
// The scroll-driven animation without an underlying value and make it go from
// the active phase to the before phase.
addAsyncAnimTest(async function() {
let scroller = new_scroller();
new_div("animation: always_fifty 5s linear 5s; " +
"animation-timeline: --scroll_timeline;");
await waitForPaintsFlushed();
// NOTE: getOMTAStyle() can't detect the animation is running on the
// compositor during the delay phase.
omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.Either,
"The scroll animation is in delay");
scroller.scrollTop = 75;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
"scroll transform animations should runs on compositor " +
"thread");
// Use setAsyncScrollOffset() to update apz (compositor thread only) to make
// sure Bug 1776077 is reproducible.
let utils = SpecialPowers.wrap(window).windowUtils;
utils.setAsyncScrollOffset(scroller, 0, -50);
utils.advanceTimeAndRefresh(16);
utils.restoreNormalRefresh();
await waitForPaints();
// NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
// OMTA style directly.
let compositorStr =
SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
ok(compositorStr === "matrix(1, 0, 0, 1, 0, 0)",
"scroll animations is in delay phase before calling main thread style " +
"udpate");
done_div();
done_scroller();
});
// The scroll-driven animation is in delay, together with other runing
// animations.
addAsyncAnimTest(async function() {
let scroller = new_scroller();
new_div("animation: transform_anim 10s linear -5s paused, " +
" always_fifty 5s linear 5s; " +
"animation-timeline: auto, --scroll_timeline;");
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor,
"The scroll animation is in delay");
scroller.scrollTop = 75;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor,
"scroll transform animations should runs on compositor " +
"thread");
let utils = SpecialPowers.wrap(window).windowUtils;
utils.setAsyncScrollOffset(scroller, 0, -50);
utils.advanceTimeAndRefresh(16);
utils.restoreNormalRefresh();
await waitForPaints();
// NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the
// OMTA style directly.
let compositorStr =
SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform");
ok(compositorStr === "matrix(1, 0, 0, 1, 100, 0)",
"scroll animations is in delay phase before calling main thread style " +
"udpate");
done_div();
done_scroller();
});
addAsyncAnimTest(async function() {
let iframe = document.createElement("iframe");
iframe.setAttribute("style", "width: 200px; height: 200px");
iframe.setAttribute("scrolling", "no");
iframe.srcdoc =
"<!DOCTYPE HTML>" +
"<html style='min-height: 100%; padding-bottom: 100px;'>" +
"<style>" +
"@keyframes anim { from, to { transform: translate(50px) } }" +
"</style>" +
"<div id='target_in_iframe' " +
" style='width:50px; height:50px; background:green; " +
" animation: anim 10s linear; " +
" animation-timeline: scroll(root);'>" +
"</div>" +
"</html>";
await new Promise(resolve => {
iframe.onload = resolve;
document.body.appendChild(iframe);
});
gDiv = iframe.contentDocument.getElementById("target_in_iframe");
const root = iframe.contentDocument.scrollingElement;
const maxScroll = root.scrollHeight - root.clientHeight;
root.scrollTop = 0.5 * maxScroll;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 50 }, 0, RunningOn.MainThread,
"scroll transform animations inside an iframe with " +
"scrolling:no should run on the main thread");
gDiv = null;
iframe.remove();
});
// FIXME: Bug 1818346. Support OMTA for view-timeline.
addAsyncAnimTest(async function() {
let scroller = document.createElement("div");
scroller.style.width = "100px";
scroller.style.height = "100px";
// Use hidden so we don't have scrollbar, to make sure the scrollport size
// is 100px x 100px, so view progress visibility range is 100px on block axis.
scroller.style.overflow = "hidden";
let content1 = document.createElement("div");
content1.style.height = "150px";
scroller.appendChild(content1);
let subject = document.createElement("div");
subject.style.width = "50px";
subject.style.height = "50px";
subject.style.viewTimelineName = "--view_timeline";
scroller.appendChild(subject);
// Let |target| be the child of |subject|, so view-timeline-name property of
// |subject| is referenceable.
let target = document.createElement("div");
target.style.width = "10px";
target.style.height = "10px";
subject.appendChild(target);
gDiv = target;
let content2 = document.createElement("div");
content2.style.height = "150px";
scroller.appendChild(content2);
// So the DOM tree looks like this:
// <div class=scroller> <!-- "scroller", height: 100px; -->
// <div></div> <!-- "", height: 150px -->
// <div></div> <!-- "subject", height: 50px; -->
// <div></div> <!-- "", height: 150px; -->
// </div>
// The subject is in view when scroller.scrollTop is [50px, 200px].
document.getElementById("display").appendChild(scroller);
await waitForPaintsFlushed();
scroller.scrollTop = 0;
target.style.animation = "transform_anim 10s linear";
target.style.animationTimeline = "--view_timeline";
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.OnMainThread,
"The scroll animation is out of view");
scroller.scrollTop = 50;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.OnMainThread,
"The scroll animation is 0%");
scroller.scrollTop = 125;
await waitForPaintsFlushed();
omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.OnMainThread,
"The scroll animation is 50%");
target.remove();
content2.remove();
subject.remove();
content1.remove();
scroller.remove();
gDiv = null;
});
</script>
</html>