40%,
100% {
background-position: 36px center;
}
}
@keyframes opacity {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes opacity-from-zero {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes opacity-without-end-value {
from { opacity: 0; }
}
@keyframes on-main-thread {
from { z-index: 0; }
to { z-index: 999; }
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes move-in {
from { transform: translate(120%, 120%); }
to { transform: translate(0%, 0%); }
}
@keyframes background-color {
from { background-color: rgb(255, 0, 0,); }
to { background-color: rgb(0, 255, 0,); }
} div {
/* Element needs geometry to be eligible for layerization */
width: 100px;
height: 100px;
background-color: white;
} progress:not(.stop)::-moz-progress-bar {
animation: on-main-thread 100s;
} body {
/*
* set overflow:hidden to avoid accidentally unthrottling animations to update
* the overflow region.
*/
overflow: hidden;
}
</style>
</head>
<body>
<script> 'use strict';
// Returns observed animation restyle markers when |funcToMakeRestyleHappen|
// is called.
// NOTE: This function is synchronous version of the above observeStyling().
// Unlike the above observeStyling, this function takes a callback function,
// |funcToMakeRestyleHappen|, which may be expected to trigger a synchronous
// restyles, and returns any restyle markers produced by calling that function.
function observeAnimSyncStyling(funcToMakeRestyleHappen) {
let priorAnimationTriggeredRestyles = SpecialPowers.DOMWindowUtils.animationTriggeredRestyles;
function ensureElementRemoval(aElement) {
return new Promise(resolve => {
aElement.remove();
waitForAllPaintsFlushed(resolve);
});
}
function waitForWheelEvent(aTarget) {
return new Promise(resolve => {
// Get the scrollable target element position in this window coordinate
// system to send a wheel event to the element.
const targetRect = aTarget.getBoundingClientRect();
const centerX = targetRect.left + targetRect.width / 2;
const centerY = targetRect.top + targetRect.height / 2;
// We need to wait for all paints before running tests to avoid contaminations
// from styling of this document itself.
waitForAllPaints(async () => {
const vsyncRate = await estimateVsyncRate();
// In this test we basically observe restyling counts in 5 frames, if it
// takes over 200ms during the 5 frames, this test will fail. So
// "200ms / 5 = 40ms" is a threshold whether the test works as expected or
// not. We'd take 5ms additional tolerance here.
// Note that the 200ms is a period we unthrottle throttled animations that
// at least one of the animating styles produces change hints causing
// overflow, the value is defined in
// KeyframeEffect::OverflowRegionRefreshInterval.
if (vsyncRate > 40 - 5) {
ok(true, `the vsync rate ${vsyncRate} on this machine is too slow to run this test`);
SimpleTest.finish();
return;
}
add_task(async function restyling_for_main_thread_animations() {
const div = addDiv(null, { style: 'animation: on-main-thread 100s' });
const animation = div.getAnimations()[0];
const restyleCount = await observeStyling(5);
is(restyleCount, 5, 'CSS animations running on the main-thread should update style ' + 'on the main thread');
await ensureElementRemoval(div);
});
add_task(async function restyling_for_main_thread_animations_progress_bar_pseudo() {
const progress = document.createElement("progress");
document.body.appendChild(progress);
let restyleCount;
restyleCount = await observeStyling(5);
// TODO(bug 1784931): Figure out why we only see four markers sometimes.
// That's not the point of this test tho.
let maybe_todo_is = restyleCount == 4 ? todo_is : is;
maybe_todo_is(restyleCount, 5, 'CSS animations running on the main-thread should update style ' + 'on the main thread on ::-moz-progress-bar'); progress.classList.add("stop");
await waitForNextFrame();
await waitForNextFrame();
const restyleCount = await observeStyling(5);
is(restyleCount, 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() {
const div = addDiv(null, { style: 'transition: opacity 100s; opacity: 0' });
getComputedStyle(div).opacity; div.style.opacity = 1;
const restyleCount = await observeStyling(5);
is(restyleCount, 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() {
const div = addDiv(null, { style: 'animation: opacity 100s' });
const animation = div.getAnimations()[0];
const restyleCount = await observeStyling(5);
is(restyleCount, 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() {
const div = addDiv(null, { style: 'animation: opacity 100s' });
const animation = div.getAnimations()[0];
let restyleCount;
restyleCount = await observeStyling(1);
is(restyleCount, 1, 'Animations running on the compositor should only update style once ' + 'after finish() is called');
restyleCount = await observeStyling(1);
todo_is(restyleCount, 0, 'Bug 1415457: Animations running on the compositor should only ' + 'update style once after finish() is called');
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Finished animations should never update style after one ' + 'restyle happened for finish()');
await ensureElementRemoval(div);
});
add_task(async function no_restyling_mouse_movement_on_finished_transition() {
const div = addDiv(null, { style: 'transition: opacity 1ms; opacity: 0' });
getComputedStyle(div).opacity; div.style.opacity = 1;
await animation.finished;
let restyleCount;
restyleCount = await observeStyling(1);
is(restyleCount, 1, 'Finished transitions should restyle once after Animation.finished ' + 'was fulfilled');
let mouseX = initialRect.left + initialRect.width / 2;
let mouseY = initialRect.top + initialRect.height / 2;
restyleCount = await observeStyling(5, () => {
// We can't use synthesizeMouse here since synthesizeMouse causes
// layout flush.
synthesizeMouseAtPoint(mouseX++, mouseY++,
{ type: 'mousemove' }, window);
});
is(restyleCount, 0, 'Finished transitions should never cause restyles when mouse is moved ' + 'on the transitions');
await ensureElementRemoval(div);
});
add_task(async function no_restyling_mouse_movement_on_finished_animation() {
const div = addDiv(null, { style: 'animation: opacity 1ms' });
const animation = div.getAnimations()[0];
const initialRect = div.getBoundingClientRect();
await animation.finished;
let restyleCount;
restyleCount = await observeStyling(1);
is(restyleCount, 1, 'Finished animations should restyle once after Animation.finished ' + 'was fulfilled');
let mouseX = initialRect.left + initialRect.width / 2;
let mouseY = initialRect.top + initialRect.height / 2;
restyleCount = await observeStyling(5, () => {
// We can't use synthesizeMouse here since synthesizeMouse causes
// layout flush.
synthesizeMouseAtPoint(mouseX++, mouseY++,
{ type: 'mousemove' }, window);
});
is(restyleCount, 0, 'Finished animations should never cause restyles when mouse is moved ' + 'on the animations');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function no_restyling_compositor_animations_out_of_view_element() {
const div = addDiv(null,
{ style: 'animation: opacity 100s; transform: translateY(-400px);' });
const animation = div.getAnimations()[0];
is(restyleCount, 0, 'Animations running on the compositor in an out-of-view element ' + 'should never cause restyles');
await ensureElementRemoval(div);
});
add_task(async function no_restyling_main_thread_animations_out_of_view_element() {
const div = addDiv(null,
{ style: 'animation: on-main-thread 100s; transform: translateY(-400px);' });
const animation = div.getAnimations()[0];
is(restyleCount, 0, 'Animations running on the main-thread in an out-of-view element ' + 'should never cause restyles');
await ensureElementRemoval(div);
});
is(restyleCount, 0, 'Opacity animations on scrolled out elements should never cause ' + 'restyles even if the animation has missing keyframes');
await ensureElementRemoval(parentElement);
}
);
add_task(
async function restyling_transform_animations_in_scrolled_out_element() {
// Make sure we start from the state right after requestAnimationFrame.
await waitForFrame();
ok(!animation.isRunningOnCompositor, 'The transform animation is not running on the compositor');
let restyleCount
let now;
let elapsed;
while (true) {
now = document.timeline.currentTime;
elapsed = (now - timeAtStart);
restyleCount = await observeStyling(1);
if (restyleCount) {
break;
}
}
// If the current time has elapsed over 200ms since the animation was
// created, it means that the animation should have already
// unthrottled in this tick, let's see what we observe in this tick's
// restyling process.
// We use toPrecision here and below so 199.99999999999977 will turn into 200.
ok(elapsed.toPrecision(10) >= 200, 'Transform animation running on the element which is scrolled out ' + 'should be throttled until 200ms is elapsed. now: ' +
now + ' start time: ' + timeAtStart + ' elapsed:' + elapsed);
let expectedMarkersLengthValid;
// On the fence of 200 ms, we probably have 1 marker; but if we hit a bad rounding
// we might still have 0. But if it's > 200, we should have 1; and less we should have 0.
if (elapsed.toPrecision(10) == 200)
expectedMarkersLengthValid = restyleCount < 2;
else if (elapsed.toPrecision(10) > 200)
expectedMarkersLengthValid = restyleCount == 1;
else
expectedMarkersLengthValid = !restyleCount;
ok(expectedMarkersLengthValid, 'Transform animation running on the element which is scrolled out ' + 'should be unthrottled after around 200ms have elapsed. now: ' +
now + ' start time: ' + timeAtStart + ' elapsed: ' + elapsed);
await ensureElementRemoval(parentElement);
}
);
add_task(
async function restyling_out_of_view_transform_animations_in_another_element() {
// Make sure we start from the state right after requestAnimationFrame.
await waitForFrame();
ok(!animation.isRunningOnCompositor, 'The transform animation on out of view element ' + 'is not running on the compositor');
// Structure copied from restyling_transform_animations_in_scrolled_out_element
let restyleCount
let now;
let elapsed;
while (true) {
now = document.timeline.currentTime;
elapsed = (now - timeAtStart);
restyleCount = await observeStyling(1);
if (restyleCount) {
break;
}
}
ok(elapsed.toPrecision(10) >= 200, 'Transform animation running on out of view element ' + 'should be throttled until 200ms is elapsed. now: ' +
now + ' start time: ' + timeAtStart + ' elapsed:' + elapsed);
let expectedMarkersLengthValid;
// On the fence of 200 ms, we probably have 1 marker; but if we hit a bad rounding
// we might still have 0. But if it's > 200, we should have 1; and less we should have 0.
if (elapsed.toPrecision(10) == 200)
expectedMarkersLengthValid = restyleCount < 2;
else if (elapsed.toPrecision(10) > 200)
expectedMarkersLengthValid = restyleCount == 1;
else
expectedMarkersLengthValid = !restyleCount;
ok(expectedMarkersLengthValid, 'Transform animation running on out of view element ' + 'should be unthrottled after around 200ms have elapsed. now: ' +
now + ' start time: ' + timeAtStart + ' elapsed: ' + elapsed);
await ensureElementRemoval(parentElement);
}
);
add_task(async function finite_transform_animations_in_out_of_view_element() {
const parentElement = addDiv(null, { style: 'overflow: hidden;' });
const div = addDiv(null);
const animation = div.animate({ transform: [ 'translateX(120%)', 'translateX(100%)' ] },
// This animation will move a bit but
// will remain out-of-view.
100 * MS_PER_SEC);
parentElement.appendChild(div);
await waitForAnimationReadyToRestyle(animation);
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor, "Should not be running in compositor");
const restyleCount = await observeStyling(20);
is(restyleCount, 20, 'Finite transform animation in out-of-view element should never be ' + 'throttled');
await waitForAnimationReadyToRestyle(animation);
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animations running on the main-thread for elements ' + 'which are scrolled out should never cause restyles');
await waitForWheelEvent(parentElement);
// Make sure we are ready to restyle before counting restyles.
await waitForFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on the main-thread which were in scrolled out ' + 'elements should update restyling soon after the element moved in ' + 'view by scrolling');
await ensureElementRemoval(parentElement);
});
add_task(async function restyling_main_thread_animations_in_nested_scrolled_out_element() {
const grandParent = addDiv(null,
{ style: 'overflow-y: scroll; height: 20px;' });
const parentElement = addDiv(null,
{ style: 'overflow-y: scroll; height: 100px;' });
const div = addDiv(null,
{ style: 'animation: on-main-thread 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);
const animation = div.getAnimations()[0];
await waitForAnimationReadyToRestyle(animation);
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animations running on the main-thread which are in nested elements ' + 'which are scrolled out should never cause restyles');
await waitForWheelEvent(grandParent);
await waitForFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on the main-thread which were in nested scrolled ' + 'out elements should update restyle soon after the element moved ' + 'in view by scrolling');
await ensureElementRemoval(grandParent);
});
add_task_if_omta_enabled(async function no_restyling_compositor_animations_in_visibility_hidden_element() {
const div = addDiv(null,
{ style: 'animation: opacity 100s; visibility: hidden' });
const animation = div.getAnimations()[0];
is(restyleCount, 0, 'Animations running on the compositor in visibility hidden element ' + 'should never cause restyles');
await ensureElementRemoval(div);
});
// FIXME: We should reduce a redundant restyle here.
ok(restyleCount >= 0, 'Animations running on the main-thread which are in scrolled out ' + 'elements should throttle restyling');
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animations running on the main-thread which is in scrolled out ' + 'elements should not update restyling');
is(restyleCount, 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');
await ensureElementRemoval(parentElement);
});
add_task(
async function restyling_animations_on_visibility_changed_element_having_child() {
const div = addDiv(null,
{ style: 'animation: on-main-thread 100s;' });
const childElement = addDiv(null); div.appendChild(childElement);
const animation = div.getAnimations()[0];
await waitForAnimationReadyToRestyle(animation);
// We don't check the animation causes restyles here since we already
// check it in the first test case.
const restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Animations running on visibility hidden element which ' + 'has a child whose visiblity is inherited from the element and ' + 'the element was initially visible');
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running that was on visibility hidden element which ' + 'gets visible should not throttle restyling any more');
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations that was in visibility hidden parent should not ' + 'throttle restyling any more');
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element but the element has ' + 'a visible child should not throttle restyling');
restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Animations running on visibility hidden element that a child ' + 'has become invisible should throttle restyling');
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element should not throttle ' + 'restyling after the invisible element changed to visible');
childElement.remove();
await waitForNextFrame();
restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Animations running on visibility hidden element should throttle ' + 'restyling again after all visible descendants were removed');
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animations running on visibility hidden element which has an ' + 'out-of-flow child should throttle restyling');
await ensureElementRemoval(div);
}
);
add_task(
async function restyling_animations_on_visibility_hidden_element_having_grandchild() {
// element tree:
//
// root(visibility:hidden)
// / \
// childA childB
// / \ / \
// AA AB BA BB
await waitForAnimationReadyToRestyle(animation);
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animations on visibility hidden element having no visible ' + 'descendants should never cause restyles');
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element but the element has ' + 'visible children should not throttle restyling');
// Make childA hidden again but both of grandchildAA and grandchildAB are
// still visible.
childA.style.visibility = 'hidden';
await waitForNextFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element that a child has ' + 'become invisible again but there are still visible children should ' + 'not throttle restyling');
// Make grandchildAA hidden but grandchildAB is still visible.
grandchildAA.style.visibility = 'hidden';
await waitForNextFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element that a grandchild ' + 'become invisible again but another grandchild is still visible ' + 'should not throttle restyling');
// Make childB and grandchildBA visible.
childB.style.visibility = 'visible';
grandchildBA.style.visibility = 'visible';
await waitForNextFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element but the element has ' + 'visible descendants should not throttle restyling');
// Make childB hidden but grandchildAB and grandchildBA are still visible.
childB.style.visibility = 'hidden';
await waitForNextFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element but the element has ' + 'visible grandchildren should not throttle restyling');
// Make grandchildAB hidden but grandchildBA is still visible.
grandchildAB.style.visibility = 'hidden';
await waitForNextFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations running on visibility hidden element but the element has ' + 'a visible grandchild should not throttle restyling');
// Make grandchildBA hidden. Now all descedants are invisible.
grandchildBA.style.visibility = 'hidden';
await waitForNextFrame();
restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Animations on visibility hidden element that all descendants have ' + 'become invisible again should never cause restyles');
// Make childB visible.
childB.style.visibility = 'visible';
await waitForNextFrame();
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animations on visibility hidden element that has a visible child ' + 'should never cause restyles');
// Make childB invisible again
childB.style.visibility = 'hidden';
await waitForNextFrame();
restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Animations on visibility hidden element that the visible child ' + 'has become invisible again should never cause restyles');
await ensureElementRemoval(div);
}
);
add_task_if_omta_enabled(async function no_restyling_compositor_animations_after_pause_is_called() {
const div = addDiv(null, { style: 'animation: opacity 100s' });
const animation = div.getAnimations()[0];
await animation.ready;
let restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Paused animations running on the compositor should never cause ' + 'restyles');
await ensureElementRemoval(div);
});
add_task(async function no_restyling_main_thread_animations_after_pause_is_called() {
const div = addDiv(null, { style: 'animation: on-main-thread 100s' });
const animation = div.getAnimations()[0];
await waitForAnimationReadyToRestyle(animation);
animation.pause();
await animation.ready;
let restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Paused animations running on the main-thread should never cause ' + 'restyles');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(async function only_one_restyling_when_current_time_is_set_to_middle_of_duration() {
const div = addDiv(null, { style: 'animation: opacity 100s' });
const animation = div.getAnimations()[0];
await waitForAnimationReadyToRestyle(animation);
animation.currentTime = 50 * MS_PER_SEC;
const restyleCount = await observeStyling(5);
is(restyleCount, 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);
});
// 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);
// Extend the duration.
animation.effect.updateTiming({ duration: 800 * MS_PER_SEC });
const restyleCount = await observeStyling(5);
is(restyleCount, 1, 'Animations running on the compositor should update style ' + 'when duration is made longer than the current time');
// We need to wait a frame to apply display:none style.
await waitForNextFrame();
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');
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Script animations on "display: none" element should not update styles');
div.style.display = '';
restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Script animations restored from "display: none" state should update ' + 'styles');
// We need to wait a frame to apply display:none style.
await waitForNextFrame();
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');
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Opacity script animations on "display: none" element should not ' + 'update styles');
div.style.display = '';
restyleCount = await observeStyling(1);
is(restyleCount, 1, 'Script animations restored from "display: none" state should update ' + 'styles soon');
ok(SpecialPowers.wrap(animation).isRunningOnCompositor, 'Opacity script animations restored from "display: none" should be ' + 'run on the compositor in the next frame');
await ensureElementRemoval(div);
});
add_task(async function restyling_for_empty_keyframes() {
const div = addDiv(null);
const animation = div.animate({ }, 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animations with no keyframes should not cause restyles');
is(restyleCount, 1, 'Setting an empty set of keyframes should trigger a single restyle ' + '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() {
const div = addDiv(null, { style: 'animation: opacity 100s' });
const animation = div.getAnimations()[0];
await waitForAnimationReadyToRestyle(animation);
ok(SpecialPowers.wrap(animation).isRunningOnCompositor);
// Apply the same animation style div.style.animation = 'opacity 100s';
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Applying same animation style ' + 'should never cause restyles');
await ensureElementRemoval(div);
});
add_task(async function necessary_update_should_be_invoked() {
const div = addDiv(null, { style: 'animation: on-main-thread 100s' });
const animation = div.getAnimations()[0];
await waitForAnimationReadyToRestyle(animation);
await waitForAnimationFrames(5);
// Apply another animation style div.style.animation = 'on-main-thread 110s';
const restyleCount = await observeStyling(1);
// There should be two restyles.
// 1) Animation-only restyle for before applying the new animation style
// 2) Animation-only restyle for after applying the new animation style
is(restyleCount, 2, 'Applying animation style with different duration ' + 'should restyle twice');
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(
async function changing_cascading_result_for_main_thread_animation() {
const div = addDiv(null, { style: 'on-main-thread: blue' });
const animation = div.animate({ opacity: [0, 1],
zIndex: ['0', '999'] },
100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
ok(SpecialPowers.wrap(animation).isRunningOnCompositor, 'The opacity animation is running on the compositor');
// Make the z-index style as !important to cause an update
// to the cascade.
// Bug 1300982: The z-index animation should be no longer
// running on the main thread. div.style.setProperty('z-index', '0', 'important');
const restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Changing cascading result for the property running on the main ' + 'thread does not cause synchronization layer of opacity animation ' + 'running on the compositor');
await ensureElementRemoval(div);
}
);
add_task_if_omta_enabled(
async function animation_visibility_and_opacity() {
const div = addDiv(null);
const animation1 = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
const animation2 = div.animate({ visibility: ['hidden', 'visible'] }, 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation1);
await waitForAnimationReadyToRestyle(animation2);
const restyleCount = await observeStyling(5);
is(restyleCount, 5, 'The animation should not be throttled');
await ensureElementRemoval(div);
}
);
div.remove();
let restyleCount;
restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Animation on orphaned element should not cause restyles');
is(restyleCount, 5, 'Animation on re-attached to the document begins to update style, got ' + restyleCount);
await ensureElementRemoval(div);
});
add_task_if_omta_enabled(
// Tests that if we remove an element from the document whose animation
// cascade needs recalculating, that it is correctly updated when it is
// re-attached to the document.
async function restyling_for_opacity_animation_on_re_attached_element() {
const div = addDiv(null, { style: 'opacity: 1 ! important' });
const animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
ok(!SpecialPowers.wrap(animation).isRunningOnCompositor, '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();
const restyleCount = await observeStyling(5);
is(restyleCount, 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.
await waitForNextFrame();
ok(SpecialPowers.wrap(animation).isRunningOnCompositor, 'The opacity animation which is no longer overridden by the ' + '!important rule begins running on the compositor even if the ' + '!important rule had been dropped before the target element was ' + 'removed');
is(restyleCount, 0, 'Additive animation has no keyframe whose offset is 0 or 1 in an ' + 'out-of-view element should be throttled');
await ensureElementRemoval(div);
}
);
// Tests that missing keyframes animations don't throttle at all.
add_task(async function no_throttling_animations_out_of_view_element() {
const div = addDiv(null, { style: 'transform: translateY(-400px);' });
const animation = div.animate([{ visibility: 'visible' }], 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Discrete animation has has no keyframe whose offset is 0 or 1 in an ' + 'out-of-view element should be throttled');
await ensureElementRemoval(div);
});
// Tests that missing keyframes animation on scrolled out element that the
// animation is not able to be throttled.
add_task(
async function no_throttling_missing_keyframe_animations_out_of_view_element() {
const div =
addDiv(null, { style: 'transform: translateY(-400px);' + 'visibility: collapse;' });
const animation = div.animate([{ visibility: 'visible' }], 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'visibility animation has no keyframe whose offset is 0 or 1 in an ' + 'out-of-view element should be throttled');
await ensureElementRemoval(div);
}
);
// Counter part of the above test.
add_task(async function no_restyling_discrete_animations_out_of_view_element() {
const div = addDiv(null, { style: 'transform: translateY(-400px);' });
const animation = div.animate({ visibility: ['visible', 'hidden'] }, 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'Discrete animation running on the main-thread in an out-of-view ' + 'element should never cause restyles');
await ensureElementRemoval(div);
});
// We possibly expect one restyle from the initial animation compose, in
// order to update animations, but nothing else.
ok(restyleCount <= 1, 'Animation running on the main-thread while computed timing is not ' + 'changed should not cause extra restyles, got ' + restyleCount);
await ensureElementRemoval(div);
});
const restyleCount = await observeStyling(5);
is(restyleCount, 5, 'CSS animations on an in-view svg element with post-transform should ' + 'not be throttled.');
const restyleCount = await observeStyling(5);
is(restyleCount, 5, 'CSS animations on an in-view svg element which is inside transformed ' + 'parent should not be throttled.');
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'CSS animations on an out-of-view svg element with post-transform ' + 'should be throttled.');
const restyleCount = await observeStyling(5);
is(restyleCount, 5, 'CSS animation on an in-view element with pre-transform should not ' + 'be throttled.');
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'CSS animation on an out-of-view element with pre-transform should be ' + 'throttled.');
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'CSS animation in an out-of-view position absolute element should ' + 'be throttled');
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'CSS animation on an out-of-view position absolute element should ' + 'be throttled');
const restyleCount = await observeStylingInTargetWindow(iframe.contentWindow, 5);
is(restyleCount, 5, 'CSS animation on an in-view position:fixed element should NOT be ' + 'throttled');
const restyleCount = await observeStylingInTargetWindow(iframe.contentWindow, 5);
is(restyleCount, 0, 'Animation on position:absolute element in collapsed iframe should ' + 'be throttled');
const restyleCount = await observeStyling(5);
is(restyleCount, 5, 'Animation on position:absolute element in collapsed element ' + 'should not be throttled');
const restyleCount = await observeStyling(5);
todo_is(restyleCount, 0, 'Animation on collapsed position:absolute element in collapsed ' + 'element should be throttled');
await ensureElementRemoval(parent);
}
);
add_task_if_omta_enabled(
async function no_restyling_for_compositor_animation_on_unrelated_style_change() {
const div = addDiv(null);
const animation = div.animate({ opacity: [0, 1] }, 100 * MS_PER_SEC);
await waitForAnimationReadyToRestyle(animation);
ok(SpecialPowers.wrap(animation).isRunningOnCompositor, 'The opacity animation is running on the compositor');
div.style.setProperty('color', 'blue', '');
const restyleCount = await observeStyling(5);
is(restyleCount, 0, 'The opacity animation keeps running on the compositor when ' + 'color style is changed');
await ensureElementRemoval(div);
}
);
const restyleCount = await observeStyling(20);
is(restyleCount, 0, 'No-overflow transform animations running on the compositor should ' + 'never update style on the main thread');
const restyleCount = observeAnimSyncStyling(() => {
is(div.getAnimations().length, 1, 'There should be one animation');
});
is(restyleCount, 0, 'Element.getAnimations() should not flush throttled animation style');
await ensureElementRemoval(div);
});
add_task(async function restyling_for_throttled_animation_on_getAnimations() {
const div = addDiv(null, { style: 'animation: opacity 100s' });
const animation = div.getAnimations()[0];
const restyleCount = observeAnimSyncStyling(() => { div.style.animationDuration = '0s';
is(div.getAnimations().length, 0, 'There should be no animation');
});
is(restyleCount, 1, // For discarding the throttled animation. 'Element.getAnimations() should flush throttled animation style so ' + 'that the throttled animation is discarded');
const restyleCount = observeAnimSyncStyling(() => {
sibling.style.opacity = '0.5';
is(animation.playState, 'running', 'Animation.playState should be running');
});
is(restyleCount, 0, 'Animation.playState should not flush throttled animation in the ' + 'case where there are only style changes that don\'t affect the ' + 'throttled animation');
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.