--- a/dom/animation/test/mozilla/file_restyles.html
+++ b/dom/animation/test/mozilla/file_restyles.html
@@ -92,16 +92,24 @@ function waitForWheelEvent(aTarget) {
{ deltaMode: WheelEvent.DOM_DELTA_PIXEL,
deltaY: targetRect.height },
() => {
requestAnimationFrame(resolve);
});
});
}
+// A wrapper function that waits for a paint and ensures where we are in the
+// state just after requestAnimationFrame callback.
+function waitForPaintsAndFrame() {
+ return waitForPaints().then(() => {
+ return waitForFrame();
+ });
+}
+
var omtaEnabled = isOMTAEnabled();
var isAndroid = !!navigator.userAgent.includes("Android");
var isServo = isStyledByServo();
var offscreenThrottlingEnabled =
SpecialPowers.getBoolPref('dom.animations.offscreen-throttling');
function add_task_if_omta_enabled(test) {
@@ -114,78 +122,79 @@ function add_task_if_omta_enabled(test)
// We need to wait for all paints before running tests to avoid contaminations
// from styling of this document itself.
waitForAllPaints(() => {
add_task(async function restyling_for_main_thread_animations() {
var div = addDiv(null, { style: 'animation: background-color 100s' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
+
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
var markers = await observeStyling(5);
is(markers.length, 5,
'CSS animations running on the main-thread should update style ' +
'on the main thread');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function no_restyling_for_compositor_animations() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
var markers = await observeStyling(5);
is(markers.length, 0,
'CSS animations running on the compositor should not update style ' +
'on the main thread');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async 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];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
var markers = await observeStyling(5);
is(markers.length, 0,
'CSS transitions running on the compositor should not update style ' +
'on the main thread');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function no_restyling_when_animation_duration_is_changed() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
div.animationDuration = '200s';
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations running on the compositor should not update style ' +
'on the main thread');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function only_one_restyling_after_finish_is_called() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
animation.finish();
var markers = await observeStyling(5);
is(markers.length, 1,
'Animations running on the compositor should only update style ' +
'once after finish() is called');
@@ -195,16 +204,18 @@ waitForAllPaints(() => {
add_task(async 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();
+ await waitForPaintsAndFrame();
+
await animation.finished;
var mouseX = initialRect.left + initialRect.width / 2;
var mouseY = initialRect.top + initialRect.height / 2;
var markers = await observeStyling(5, () => {
// We can't use synthesizeMouse here since synthesizeMouse causes
// layout flush.
synthesizeMouseAtPoint(mouseX++, mouseY++,
@@ -218,16 +229,18 @@ waitForAllPaints(() => {
});
add_task(async 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();
+ await waitForPaintsAndFrame();
+
await animation.finished;
var mouseX = initialRect.left + initialRect.width / 2;
var mouseY = initialRect.top + initialRect.height / 2;
var markers = await observeStyling(5, () => {
// We can't use synthesizeMouse here since synthesizeMouse causes
// layout flush.
synthesizeMouseAtPoint(mouseX++, mouseY++,
@@ -244,17 +257,17 @@ waitForAllPaints(() => {
if (!offscreenThrottlingEnabled) {
return;
}
var div = addDiv(null,
{ style: 'animation: opacity 100s; transform: translateY(-400px);' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations running on the compositor in an out-of-view element ' +
'should never cause restyles');
await ensureElementRemoval(div);
@@ -264,17 +277,17 @@ waitForAllPaints(() => {
if (!offscreenThrottlingEnabled) {
return;
}
var div = addDiv(null,
{ style: 'animation: background-color 100s; transform: translateY(-400px);' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations running on the main-thread in an out-of-view element ' +
'should never cause restyles');
await ensureElementRemoval(div);
});
@@ -292,17 +305,17 @@ waitForAllPaints(() => {
}
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];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations running on the compositor for elements ' +
'which are scrolled out should never cause restyles');
await ensureElementRemoval(parentElement);
@@ -366,17 +379,17 @@ waitForAllPaints(() => {
var parentElement = addDiv(null,
{ style: 'overflow-y: scroll; height: 20px;' });
var div = addDiv(null,
{ style: 'animation: background-color 100s; position: relative; top: 20px;' });
parentElement.appendChild(div);
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations running on the main-thread for elements ' +
'which are scrolled out should never cause restyles');
await waitForWheelEvent(parentElement);
@@ -410,17 +423,17 @@ waitForAllPaints(() => {
{ style: 'animation: background-color 100s; ' +
'position: relative; ' +
'top: 20px;' }); // This element is in-view in the parent, but
// out of view in the grandparent.
grandParent.appendChild(parentElement);
parentElement.appendChild(div);
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations running on the main-thread which are in nested elements ' +
'which are scrolled out should never cause restyles');
await waitForWheelEvent(grandParent);
@@ -433,17 +446,17 @@ waitForAllPaints(() => {
await ensureElementRemoval(grandParent);
});
add_task_if_omta_enabled(async function no_restyling_compositor_animations_in_visiblily_hidden_element() {
var div = addDiv(null,
{ style: 'animation: opacity 100s; visibility: hidden' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
var markers = await observeStyling(5);
todo_is(markers.length, 0,
'Bug 1237454: Animations running on the compositor in ' +
'visibility hidden element should never cause restyles');
await ensureElementRemoval(div);
@@ -467,17 +480,17 @@ waitForAllPaints(() => {
var div = addDiv(null,
{ style: 'animation: background-color 100s;' });
var pad = addDiv(null,
{ style: 'height: 400px;' });
parentElement.appendChild(div);
parentElement.appendChild(pad);
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
await waitForWheelEvent(parentElement);
var markers = await observeStyling(5);
// FIXME: We should reduce a redundant restyle here.
ok(markers.length >= 0,
'Animations running on the main-thread which are in scrolled out ' +
@@ -493,17 +506,17 @@ waitForAllPaints(() => {
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];
- await animation.ready;
+ await waitForPaintsAndFrame();
parentElement.style.height = '100px';
var markers = await observeStyling(1);
is(markers.length, 1,
'Animations running on the main-thread which was in scrolled out ' +
'elements should update restyling soon after the element moved in ' +
'view by resizing');
@@ -511,17 +524,17 @@ waitForAllPaints(() => {
await ensureElementRemoval(parentElement);
});
add_task(async 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];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
todo_is(markers.length, 0,
'Bug 1237454: Animations running on the main-thread in ' +
'visibility hidden element should never cause restyles');
await ensureElementRemoval(div);
});
@@ -529,62 +542,62 @@ waitForAllPaints(() => {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
await animation.ready;
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
animation.pause();
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Bug 1232563: Paused animations running on the compositor should ' +
'never cause restyles once after pause() is called');
await ensureElementRemoval(div);
});
add_task(async function no_restyling_main_thread_animations_after_pause_is_called() {
var div = addDiv(null, { style: 'animation: background-color 100s' });
var animation = div.getAnimations()[0];
await animation.ready;
animation.pause();
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Bug 1232563: Paused animations running on the main-thread should ' +
'never cause restyles after pause() is called');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async 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];
- await animation.ready;
+ await waitForPaintsAndFrame();
animation.currentTime = 50 * MS_PER_SEC;
var markers = await 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');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function change_duration_and_currenttime() {
var div = addDiv(null);
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(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(!SpecialPowers.wrap(animation).isRunningOnCompositor);
@@ -603,17 +616,17 @@ waitForAllPaints(() => {
var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
100 * MS_PER_SEC);
await animation.ready;
div.style.display = 'none';
// We need to wait a frame to apply display:none style.
- await waitForFrame();
+ await waitForPaintsAndFrame();
is(animation.playState, 'running',
'Script animations keep running even when the target element has ' +
'"display: none" style');
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,
'Script animations on "display:none" element should not run on the ' +
'compositor');
@@ -639,17 +652,17 @@ waitForAllPaints(() => {
var div = addDiv(null);
var animation = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
await animation.ready;
div.style.display = 'none';
// We need to wait a frame to apply display:none style.
- await waitForFrame();
+ await waitForPaintsAndFrame();
is(animation.playState, 'running',
'Opacity script animations keep running even when the target element ' +
'has "display: none" style');
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor,
'Opacity script animations on "display:none" element should not ' +
'run on the compositor');
@@ -670,17 +683,17 @@ waitForAllPaints(() => {
await ensureElementRemoval(div);
});
add_task(async function restyling_for_empty_keyframes() {
var div = addDiv(null);
var animation = div.animate({ }, 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Animations with no keyframes should not cause restyles');
animation.effect.setKeyframes({ backgroundColor: ['red', 'blue'] });
markers = await observeStyling(5);
@@ -696,17 +709,17 @@ waitForAllPaints(() => {
'to remove the previous animated style');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function no_restyling_when_animation_style_when_re_setting_same_animation_property() {
var div = addDiv(null, { style: 'animation: opacity 100s' });
var animation = div.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
// Apply the same animation style
div.style.animation = 'opacity 100s';
var markers = await observeStyling(5);
is(markers.length, 0,
'Applying same animation style ' +
'should never cause restyles');
await ensureElementRemoval(div);
@@ -715,30 +728,32 @@ waitForAllPaints(() => {
add_task(async function necessary_update_should_be_invoked() {
var div = addDiv(null, { style: 'animation: background-color 100s' });
var animation = div.getAnimations()[0];
await animation.ready;
await waitForAnimationFrames(5);
// Apply another animation style
div.style.animation = 'background-color 110s';
var animation = div.getAnimations()[0];
+ await waitForPaintsAndFrame();
+
var markers = await observeStyling(5);
is(markers.length, 5,
'Applying animation style with different duration ' +
'should cause restyles on every frame.');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(
async function changing_cascading_result_for_main_thread_animation() {
var div = addDiv(null, { style: 'background-color: blue' });
var animation = div.animate({ opacity: [0, 1],
backgroundColor: ['green', 'red'] },
100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
'The opacity animation is running on the compositor');
// Make the background-color style as !important to cause an update
// to the cascade.
// Bug 1300982: The background-color animation should be no longer
// running on the main thread.
div.style.setProperty('background-color', '1', 'important');
var markers = await observeStyling(5);
@@ -754,16 +769,18 @@ waitForAllPaints(() => {
var div = addDiv(null);
var animation = div.animate({ marginLeft: [ '0px', '100px' ] },
100 * MS_PER_SEC);
await animation.ready;
div.remove();
+ await waitForPaintsAndFrame();
+
var markers = await observeStyling(5);
is(markers.length, 0,
'Animation on orphaned element should not cause restyles');
document.body.appendChild(div);
markers = await observeStyling(1);
if (isServo) {
@@ -808,16 +825,18 @@ waitForAllPaints(() => {
'The opacity animation overridden by an !important rule is NOT ' +
'running on the compositor');
// Drop the !important rule to update the cascade.
div.style.setProperty('opacity', '1', '');
div.remove();
+ await waitForPaintsAndFrame();
+
var markers = await observeStyling(5);
is(markers.length, 0,
'Opacity animation on orphaned element should not cause restyles');
document.body.appendChild(div);
// Need a frame to give the animation a chance to be sent to the
// compositor.
@@ -839,17 +858,17 @@ waitForAllPaints(() => {
!SpecialPowers.getBoolPref('dom.animations-api.core.enabled')) {
return;
}
var div = addDiv(null, { style: 'transform: translateY(-400px);' });
var animation =
div.animate([{ visibility: 'visible' }], 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 5,
'Discrete animation has has no keyframe whose offset is 0 or 1 in an ' +
'out-of-view element should not be throttled');
await ensureElementRemoval(div);
});
@@ -860,33 +879,33 @@ waitForAllPaints(() => {
!SpecialPowers.getBoolPref('dom.animations-api.core.enabled')) {
return;
}
var div = addDiv(null, { style: 'transform: translateY(-400px);' });
var animation =
div.animate({ visibility: ['visible', 'hidden'] }, 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Discrete animation running on the main-thread in an out-of-view ' +
'element should never cause restyles');
await ensureElementRemoval(div);
});
add_task(async function no_restyling_while_computed_timing_is_not_changed() {
var div = addDiv(null);
var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
{ duration: 100 * MS_PER_SEC,
easing: 'step-end' });
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'Animation running on the main-thread while computed timing is not ' +
'changed should never cause restyles');
await ensureElementRemoval(div);
});
@@ -906,17 +925,17 @@ waitForAllPaints(() => {
width: '50px',
height: '50px' });
var rect = addSVGElement(svg, 'rect', { x: '-10',
y: '-10',
width: '10',
height: '10',
fill: 'red' });
var animation = rect.animate({ fill: ['blue', 'lime'] }, 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 5,
'CSS animations on an in-view svg element with post-transform should ' +
'not be throttled.');
await ensureElementRemoval(div);
});
@@ -939,17 +958,17 @@ waitForAllPaints(() => {
var svg = addSVGElement(div, 'svg', { viewBox: '-10 -10 0.1 0.1',
width: '50px',
height: '50px' });
var rect = addSVGElement(svg, 'rect', { width: '10',
height: '10',
fill: 'red' });
var animation = rect.animate({ fill: ['blue', 'lime'] }, 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'CSS animations on an out-of-view svg element with post-transform ' +
'should be throttled.');
await ensureElementRemoval(div);
});
@@ -966,17 +985,17 @@ waitForAllPaints(() => {
var scrollDiv = addDiv(null, { style: 'overflow: scroll; ' +
'height: 100px; width: 100px;' });
var targetDiv = addDiv(null,
{ style: 'animation: background-color 100s;' +
'transform: translate(-50px, -50px);' });
scrollDiv.appendChild(targetDiv);
var animation = targetDiv.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 5,
'CSS animation on an in-view element with pre-transform should not ' +
'be throttled.');
await ensureElementRemoval(scrollDiv);
});
@@ -997,32 +1016,32 @@ waitForAllPaints(() => {
var scrollDiv = addDiv(null, { style: 'overflow: scroll;' +
'height: 100px; width: 100px;' });
var targetDiv = addDiv(null,
{ style: 'animation: background-color 100s;' +
'transform: translate(100px, 100px);' });
scrollDiv.appendChild(targetDiv);
var animation = targetDiv.getAnimations()[0];
- await animation.ready;
+ await waitForPaintsAndFrame();
var markers = await observeStyling(5);
is(markers.length, 0,
'CSS animation on an out-of-view element with pre-transform should be ' +
'throttled.');
await ensureElementRemoval(scrollDiv);
});
add_task_if_omta_enabled(
async function no_restyling_for_compositor_animation_on_unrelated_style_change() {
var div = addDiv(null);
var animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
- await animation.ready;
+ await waitForPaintsAndFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor,
'The opacity animation is running on the compositor');
div.style.setProperty('color', 'blue', '');
var markers = await observeStyling(5);
if (isServo) {
is(markers.length, 0,
'The opacity animation keeps running on the compositor when ' +