<!
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 =
"" +
"" +
"" +
" +
" style='width:50px; height:50px; background:green; " +
" animation: anim 10s linear; " +
" animation-timeline: scroll(root);'>" +
"
" +
"";
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>