gecko-dev/dom/animation/test/chrome/test_restyles.html

449 строки
15 KiB
HTML

<!doctype html>
<head>
<meta charset=utf-8>
<title>Tests restyles caused by animations</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
<script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
<script src="../testcommon.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
<style>
@keyframes opacity {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes background-color {
from { background-color: red; }
to { background-color: blue; }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
div {
/* Element needs geometry to be eligible for layerization */
width: 100px;
height: 100px;
background-color: white;
}
</style>
</head>
<body>
<script>
'use strict';
function observeStyling(frameCount, onFrame) {
var docShell = window.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
.QueryInterface(SpecialPowers.Ci.nsIDocShell);
docShell.recordProfileTimelineMarkers = true;
docShell.popProfileTimelineMarkers();
return new Promise(function(resolve) {
return waitForAnimationFrames(frameCount, onFrame).then(function() {
var markers = docShell.popProfileTimelineMarkers();
docShell.recordProfileTimelineMarkers = false;
var stylingMarkers = markers.filter(function(marker, index) {
return marker.name == 'Styles' &&
(marker.restyleHint == 'eRestyle_CSSAnimations' ||
marker.restyleHint == 'eRestyle_CSSTransitions');
});
resolve(stylingMarkers);
});
});
}
function ensureElementRemoval(aElement) {
return new Promise(function(resolve) {
aElement.remove();
waitForAllPaintsFlushed(resolve);
});
}
SimpleTest.waitForExplicitFinish();
const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
SpecialPowers.getBoolPref(OMTAPrefKey);
function add_task_if_omta_enabled(test) {
if (!omtaEnabled) {
info(test.name + " is skipped because OMTA is disabled");
return;
}
add_task(test);
}
// We need to wait for all paints before running tests to avoid contaminations
// from styling of this document itself.
waitForAllPaints(function() {
add_task_if_omta_enabled(function* no_restyling_for_compositor_animations() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
yield animation.ready;
ok(animation.isRunningOnCompositor);
var markers = yield observeStyling(5);
is(markers.length, 0,
'CSS animations running on the compositor should not update style ' +
'on the main thread');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* no_restyling_for_compositor_transitions() {
var div = addDiv(null, { style: 'transition: opacity 100s; opacity: 0' });
getComputedStyle(div).opacity;
div.style.opacity = 1;
var animation = div.getAnimations()[0];
yield animation.ready;
ok(animation.isRunningOnCompositor);
var markers = yield observeStyling(5);
is(markers.length, 0,
'CSS transitions running on the compositor should not update style ' +
'on the main thread');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* no_restyling_when_animation_duration_is_changed() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
yield animation.ready;
ok(animation.isRunningOnCompositor);
div.animationDuration = '200s';
var markers = yield observeStyling(5);
is(markers.length, 0,
'Animations running on the compositor should not update style ' +
'on the main thread');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* only_one_restyling_after_finish_is_called() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
yield animation.ready;
ok(animation.isRunningOnCompositor);
animation.finish();
var markers = yield observeStyling(5);
is(markers.length, 1,
'Animations running on the compositor should only update style ' +
'once after finish() is called');
yield ensureElementRemoval(div);
});
add_task(function* no_restyling_mouse_movement_on_finished_transition() {
var div = addDiv(null, { style: 'transition: opacity 1ms; opacity: 0' });
getComputedStyle(div).opacity;
div.style.opacity = 1;
var animation = div.getAnimations()[0];
var initialRect = div.getBoundingClientRect();
yield animation.finished;
var mouseX = initialRect.left + initialRect.width / 2;
var mouseY = initialRect.top + initialRect.height / 2;
var markers = yield observeStyling(5, function() {
// We can't use synthesizeMouse here since synthesizeMouse causes
// layout flush.
synthesizeMouseAtPoint(mouseX++, mouseY++,
{ type: 'mousemove' }, window);
});
is(markers.length, 0,
'Bug 1219236: Finished transitions should never cause restyles ' +
'when mouse is moved on the animations');
yield ensureElementRemoval(div);
});
add_task(function* no_restyling_mouse_movement_on_finished_animation() {
var div = addDiv(null, { style: 'animation: opacity 1ms' });
var animation = div.getAnimations()[0];
var initialRect = div.getBoundingClientRect();
yield animation.finished;
var mouseX = initialRect.left + initialRect.width / 2;
var mouseY = initialRect.top + initialRect.height / 2;
var markers = yield observeStyling(5, function() {
// We can't use synthesizeMouse here since synthesizeMouse causes
// layout flush.
synthesizeMouseAtPoint(mouseX++, mouseY++,
{ type: 'mousemove' }, window);
});
is(markers.length, 0,
'Bug 1219236: Finished animations should never cause restyles ' +
'when mouse is moved on the animations');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* no_restyling_compositor_animations_out_of_view_element() {
var div = addDiv(null,
{ style: 'animation: opacity 100s; transform: translateY(-400px);' });
var animation = div.getAnimations()[0];
yield animation.ready;
ok(!animation.isRunningOnCompositor);
var markers = yield observeStyling(5);
todo_is(markers.length, 0,
'Bug 1166500: Animations running on the compositor in out of ' +
'view element should never cause restyles');
yield ensureElementRemoval(div);
});
add_task(function* no_restyling_main_thread_animations_out_of_view_element() {
var div = addDiv(null,
{ style: 'animation: background-color 100s; transform: translateY(-400px);' });
var animation = div.getAnimations()[0];
yield animation.ready;
var markers = yield observeStyling(5);
todo_is(markers.length, 0,
'Bug 1166500: Animations running on the main-thread in out of ' +
'view element should never cause restyles');
yield ensureElementRemoval(div);
});
/*
Disabled for now since, on Android, the opacity animation runs on the
compositor even if it is scrolled out of view.
We will fix this in bug 1166500 or a follow-up bug
add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_scrolled_out_element() {
var parentElement = addDiv(null,
{ style: 'overflow-y: scroll; height: 20px;' });
var div = addDiv(null,
{ style: 'animation: opacity 100s; position: relative; top: 100px;' });
parentElement.appendChild(div);
var animation = div.getAnimations()[0];
yield animation.ready;
ok(!animation.isRunningOnCompositor);
var markers = yield observeStyling(5);
todo_is(markers.length, 0,
'Bug 1166500: Animations running on the compositor in elements ' +
'which are scrolled out should never cause restyles');
yield ensureElementRemoval(parentElement);
});
*/
add_task(function* no_restyling_main_thread_animations_in_scrolled_out_element() {
var parentElement = addDiv(null,
{ style: 'overflow-y: scroll; height: 20px;' });
var div = addDiv(null,
{ style: 'animation: background-color 100s; position: relative; top: 100px;' });
parentElement.appendChild(div);
var animation = div.getAnimations()[0];
yield animation.ready;
var markers = yield observeStyling(5);
todo_is(markers.length, 0,
'Bug 1166500: Animations running on the main-thread in elements ' +
'which are scrolled out should never cause restyles');
yield ensureElementRemoval(parentElement);
});
/*
Disabled for now since, on Android and B2G, the opacity animation runs on the
compositor even if the associated element has visibility:hidden.
We will fix this in bug 1237454 or a follow-up bug.
add_task_if_omta_enabled(function* no_restyling_compositor_animations_in_visiblily_hidden_element() {
var div = addDiv(null,
{ style: 'animation: opacity 100s; visibility: hidden' });
var animation = div.getAnimations()[0];
yield animation.ready;
ok(!animation.isRunningOnCompositor);
var markers = yield observeStyling(5);
todo_is(markers.length, 0,
'Bug 1237454: Animations running on the compositor in ' +
'visibility hidden element should never cause restyles');
yield ensureElementRemoval(div);
});
*/
add_task(function* no_restyling_main_thread_animations_in_visiblily_hidden_element() {
var div = addDiv(null,
{ style: 'animation: background-color 100s; visibility: hidden' });
var animation = div.getAnimations()[0];
yield animation.ready;
var markers = yield observeStyling(5);
todo_is(markers.length, 0,
'Bug 1237454: Animations running on the main-thread in ' +
'visibility hidden element should never cause restyles');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* no_restyling_compositor_animations_after_pause_is_called() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
yield animation.ready;
ok(animation.isRunningOnCompositor);
animation.pause();
yield animation.ready;
var markers = yield observeStyling(5);
is(markers.length, 0,
'Bug 1232563: Paused animations running on the compositor should ' +
'never cause restyles once after pause() is called');
yield ensureElementRemoval(div);
});
add_task(function* no_restyling_main_thread_animations_after_pause_is_called() {
var div = addDiv(null, { style: 'animation: background-color 100s' });
var animation = div.getAnimations()[0];
yield animation.ready;
animation.pause();
yield animation.ready;
var markers = yield observeStyling(5);
is(markers.length, 0,
'Bug 1232563: Paused animations running on the main-thread should ' +
'never cause restyles after pause() is called');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* only_one_restyling_when_current_time_is_set_to_middle_of_duration() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
yield animation.ready;
animation.currentTime = 50 * MS_PER_SEC;
var markers = yield observeStyling(5);
is(markers.length, 1,
'Bug 1235478: Animations running on the compositor should only once ' +
'update style when currentTime is set to middle of duration time');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* change_duration_and_currenttime() {
var div = addDiv(null);
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
yield animation.ready;
ok(animation.isRunningOnCompositor);
// Set currentTime to a time longer than duration.
animation.currentTime = 500 * MS_PER_SEC;
// Now the animation immediately get back from compositor.
ok(!animation.isRunningOnCompositor);
// Extend the duration.
animation.effect.timing.duration = 800 * MS_PER_SEC;
var markers = yield observeStyling(5);
is(markers.length, 1,
'Animations running on the compositor should update style ' +
'when timing.duration is made longer than the current time');
yield ensureElementRemoval(div);
});
add_task(function* script_animation_on_display_none_element() {
var div = addDiv(null);
var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
100 * MS_PER_SEC);
yield animation.ready;
div.style.display = 'none';
// We need to wait a frame to apply display:none style.
yield waitForFrame();
is(animation.playState, 'running',
'Script animations keep running even when the target element has ' +
'"display: none" style');
ok(!animation.isRunningOnCompositor,
'Script animations on "display:none" element should not run on the ' +
'compositor');
var markers = yield observeStyling(5);
is(markers.length, 0,
'Script animations on "display: none" element should not update styles');
div.style.display = '';
// We need to wait a frame to unapply display:none style.
yield waitForFrame();
var markers = yield observeStyling(5);
is(markers.length, 5,
'Script animations restored from "display: none" state should update ' +
'styles');
yield ensureElementRemoval(div);
});
add_task_if_omta_enabled(function* compositable_script_animation_on_display_none_element() {
var div = addDiv(null);
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
yield animation.ready;
div.style.display = 'none';
// We need to wait a frame to apply display:none style.
yield waitForFrame();
is(animation.playState, 'running',
'Opacity script animations keep running even when the target element ' +
'has "display: none" style');
todo(!animation.isRunningOnCompositor,
'Opacity script animations on "display:none" element should not ' +
'run on the compositor');
var markers = yield observeStyling(5);
is(markers.length, 0,
'Opacity script animations on "display: none" element should not ' +
'update styles');
div.style.display = '';
// We need to wait a frame to unapply display:none style.
yield waitForFrame();
ok(animation.isRunningOnCompositor,
'Opacity script animations restored from "display: none" should be ' +
'run on the compositor');
yield ensureElementRemoval(div);
});
});
</script>
</body>