1576 lines
59 KiB
HTML
1576 lines
59 KiB
HTML
<!DOCTYPE html>
|
|
<meta charset=utf-8>
|
|
<title>
|
|
Test chrome-only MutationObserver animation notifications (sync tests)
|
|
</title>
|
|
<!--
|
|
|
|
This file contains synchronous tests for animation mutation observers.
|
|
|
|
In general we prefer to write synchronous tests since they are less likely to
|
|
timeout when run on automation. Tests that require asynchronous steps (e.g.
|
|
waiting on events) should be added to test_animations_observers_async.html
|
|
instead.
|
|
|
|
-->
|
|
<script type="application/javascript" src="../testharness.js"></script>
|
|
<script type="application/javascript" src="../testharnessreport.js"></script>
|
|
<script type="application/javascript" src="../testcommon.js"></script>
|
|
<div id="log"></div>
|
|
<style>
|
|
@keyframes anim {
|
|
to { transform: translate(100px); }
|
|
}
|
|
@keyframes anotherAnim {
|
|
to { transform: translate(0px); }
|
|
}
|
|
</style>
|
|
<script>
|
|
|
|
/**
|
|
* Return a new MutationObserver which observing |target| element
|
|
* with { animations: true, subtree: |subtree| } option.
|
|
*
|
|
* NOTE: This observer should be used only with takeRecords(). If any of
|
|
* MutationRecords are observed in the callback of the MutationObserver,
|
|
* it will raise an assertion.
|
|
*/
|
|
function setupSynchronousObserver(t, target, subtree) {
|
|
var observer = new MutationObserver(records => {
|
|
assert_unreached("Any MutationRecords should not be observed in this " +
|
|
"callback");
|
|
});
|
|
t.add_cleanup(() => {
|
|
observer.disconnect();
|
|
});
|
|
observer.observe(target, { animations: true, subtree });
|
|
return observer;
|
|
}
|
|
|
|
function assert_record_list(actual, expected, desc, index, listName) {
|
|
assert_equals(actual.length, expected.length,
|
|
`${desc} - record[${index}].${listName} length`);
|
|
if (actual.length != expected.length) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < actual.length; i++) {
|
|
assert_not_equals(actual.indexOf(expected[i]), -1,
|
|
`${desc} - record[${index}].${listName} contains expected Animation`);
|
|
}
|
|
}
|
|
|
|
function assert_equals_records(actual, expected, desc) {
|
|
assert_equals(actual.length, expected.length, `${desc} - number of records`);
|
|
if (actual.length != expected.length) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < actual.length; i++) {
|
|
assert_record_list(actual[i].addedAnimations,
|
|
expected[i].added, desc, i, "addedAnimations");
|
|
assert_record_list(actual[i].changedAnimations,
|
|
expected[i].changed, desc, i, "changedAnimations");
|
|
assert_record_list(actual[i].removedAnimations,
|
|
expected[i].removed, desc, i, "removedAnimations");
|
|
}
|
|
}
|
|
|
|
function runTest() {
|
|
[ { subtree: false },
|
|
{ subtree: true }
|
|
].forEach(aOptions => {
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] }, 200 * MS_PER_SEC);
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.updateTiming({ duration: 100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after duration is changed");
|
|
|
|
anim.effect.updateTiming({ duration: 100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value");
|
|
|
|
anim.currentTime = anim.effect.getComputedTiming().duration * 2;
|
|
anim.finish();
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after animation end");
|
|
|
|
anim.effect.updateTiming({
|
|
duration: anim.effect.getComputedTiming().duration * 3
|
|
});
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation restarted");
|
|
|
|
anim.effect.updateTiming({ duration: 'auto' });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after duration set \"auto\"");
|
|
|
|
anim.effect.updateTiming({ duration: 'auto' });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value \"auto\"");
|
|
}, "change_duration_and_currenttime");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.updateTiming({ endDelay: 10 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after endDelay is changed");
|
|
|
|
anim.effect.updateTiming({ endDelay: 10 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value");
|
|
|
|
anim.currentTime = 109 * MS_PER_SEC;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after currentTime during endDelay");
|
|
|
|
anim.effect.updateTiming({ endDelay: -110 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning negative value");
|
|
}, "change_enddelay_and_currenttime");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC,
|
|
endDelay: -100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after animation is added");
|
|
}, "zero_end_time");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.updateTiming({ iterations: 2 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after iterations is changed");
|
|
|
|
anim.effect.updateTiming({ iterations: 2 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value");
|
|
|
|
anim.effect.updateTiming({ iterations: 0 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after animation end");
|
|
|
|
anim.effect.updateTiming({ iterations: Infinity });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation restarted");
|
|
}, "change_iterations");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.updateTiming({ delay: 100 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after delay is changed");
|
|
|
|
anim.effect.updateTiming({ delay: 100 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value");
|
|
|
|
anim.effect.updateTiming({ delay: -100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after animation end");
|
|
|
|
anim.effect.updateTiming({ delay: 0 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation restarted");
|
|
}, "change_delay");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC,
|
|
easing: "steps(2, start)" });
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.updateTiming({ easing: "steps(2, end)" });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after easing is changed");
|
|
|
|
anim.effect.updateTiming({ easing: "steps(2, end)" });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value");
|
|
}, "change_easing");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100, delay: -100 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning negative value");
|
|
}, "negative_delay_in_constructor");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var effect = new KeyframeEffect(null,
|
|
{ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC });
|
|
var anim = new Animation(effect, document.timeline);
|
|
anim.play();
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "no records after animation is added");
|
|
}, "create_animation_without_target");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.target = div;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "no records after setting the same target");
|
|
|
|
anim.effect.target = null;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after setting null");
|
|
|
|
anim.effect.target = null;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after setting redundant null");
|
|
}, "set_redundant_animation_target");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect = null;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after animation is removed");
|
|
}, "set_null_animation_effect");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = new Animation();
|
|
anim.play();
|
|
anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
|
|
100 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
}, "set_effect_on_null_effect_animation");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
|
|
100 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
|
|
100 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after replace effects");
|
|
}, "replace_effect_targeting_on_the_same_element");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ marginLeft: [ "0px", "100px" ] },
|
|
100 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.currentTime = 60 * MS_PER_SEC;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after animation is changed");
|
|
|
|
anim.effect = new KeyframeEffect(div, { opacity: [ 0, 1 ] },
|
|
50 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after replacing effects");
|
|
}, "replace_effect_targeting_on_the_same_element_not_in_effect");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ }, 100 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.composite = "add";
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after composite is changed");
|
|
|
|
anim.effect.composite = "add";
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "no record after setting the same composite");
|
|
|
|
}, "set_composite");
|
|
|
|
// Test that starting a single animation that is cancelled by calling
|
|
// cancel() dispatches an added notification and then a removed
|
|
// notification.
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s forwards" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
animations[0].cancel();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
|
|
// Re-trigger the animation.
|
|
animations[0].play();
|
|
|
|
// Single MutationRecord for the Animation (re-)addition.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
}, "single_animation_cancelled_api");
|
|
|
|
// Test that updating a property on the Animation object dispatches a changed
|
|
// notification.
|
|
[
|
|
{ prop: "playbackRate", val: 0.5 },
|
|
{ prop: "startTime", val: 50 * MS_PER_SEC },
|
|
{ prop: "currentTime", val: 50 * MS_PER_SEC },
|
|
].forEach(aChangeTest => {
|
|
test(t => {
|
|
// We use a forwards fill mode so that even if the change we make causes
|
|
// the animation to become finished, it will still be "relevant" so we
|
|
// won't mark it as removed.
|
|
var div = addDiv(t, { style: "animation: anim 100s forwards" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Update the property.
|
|
animations[0][aChangeTest.prop] = aChangeTest.val;
|
|
|
|
// Make a redundant change.
|
|
// eslint-disable-next-line no-self-assign
|
|
animations[0][aChangeTest.prop] = animations[0][aChangeTest.prop];
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after animation property change");
|
|
}, `single_animation_api_change_${aChangeTest.prop}`);
|
|
});
|
|
|
|
// Test that making a redundant change to currentTime while an Animation
|
|
// is pause-pending still generates a change MutationRecord since setting
|
|
// the currentTime to any value in this state aborts the pending pause.
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
animations[0].pause();
|
|
|
|
// We are now pause-pending. Even if we make a redundant change to the
|
|
// currentTime, we should still get a change record because setting the
|
|
// currentTime while pause-pending has the effect of cancelling a pause.
|
|
// eslint-disable-next-line no-self-assign
|
|
animations[0].currentTime = animations[0].currentTime;
|
|
|
|
// Two MutationRecords for the Animation changes: one for pausing, one
|
|
// for aborting the pause.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] },
|
|
{ added: [], changed: animations, removed: [] }],
|
|
"records after pausing then seeking");
|
|
}, "change_currentTime_while_pause_pending");
|
|
|
|
// Test that calling finish() on a forwards-filling Animation dispatches
|
|
// a changed notification.
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s forwards" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
animations[0].finish();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after finish()");
|
|
|
|
// Redundant finish.
|
|
animations[0].finish();
|
|
|
|
// Ensure no change records.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after redundant finish()");
|
|
}, "finish_with_forwards_fill");
|
|
|
|
// Test that calling finish() on an Animation that does not fill forwards,
|
|
// dispatches a removal notification.
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
animations[0].finish();
|
|
|
|
// Single MutationRecord for the Animation removal.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after finishing");
|
|
}, "finish_without_fill");
|
|
|
|
// Test that calling finish() on a forwards-filling Animation dispatches
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animation = div.getAnimations()[0];
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [animation], changed: [], removed: []}],
|
|
"records after creation");
|
|
animation.id = "new id";
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [animation], removed: []}],
|
|
"records after id is changed");
|
|
|
|
animation.id = "new id";
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value with id");
|
|
}, "change_id");
|
|
|
|
// Test that calling reverse() dispatches a changed notification.
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s both" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
animations[0].reverse();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after calling reverse()");
|
|
}, "reverse");
|
|
|
|
// Test that calling reverse() does *not* dispatch a changed notification
|
|
// when playbackRate == 0.
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s both" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Seek to the middle and set playbackRate to zero.
|
|
animations[0].currentTime = 50 * MS_PER_SEC;
|
|
animations[0].playbackRate = 0;
|
|
|
|
// Two MutationRecords, one for each change.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] },
|
|
{ added: [], changed: animations, removed: [] }],
|
|
"records after seeking and setting playbackRate");
|
|
|
|
animations[0].reverse();
|
|
|
|
// We should get no notifications.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after calling reverse()");
|
|
}, "reverse_with_zero_playbackRate");
|
|
|
|
// Test that reverse() on an Animation does *not* dispatch a changed
|
|
// notification when it throws an exception.
|
|
test(t => {
|
|
// Start an infinite animation
|
|
var div = addDiv(t, { style: "animation: anim 10s infinite" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Shift the animation into the future such that when we call reverse
|
|
// it will try to seek to the (infinite) end.
|
|
animations[0].startTime = 100 * MS_PER_SEC;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after adjusting startTime");
|
|
|
|
// Reverse: should throw
|
|
assert_throws('InvalidStateError', () => {
|
|
animations[0].reverse();
|
|
}, 'reverse() on future infinite animation throws an exception');
|
|
|
|
// We should get no notifications.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after calling reverse()");
|
|
}, "reverse_with_exception");
|
|
|
|
// Test that attempting to start an animation that should already be finished
|
|
// does not send any notifications.
|
|
test(t => {
|
|
// Start an animation that should already be finished.
|
|
var div = addDiv(t, { style: "animation: anim 1s -2s;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause no Animations to be created.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 0,
|
|
"getAnimations().length after animation start");
|
|
|
|
// And we should get no notifications.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after attempted animation start");
|
|
}, "already_finished");
|
|
|
|
test(t => {
|
|
var div = addDiv(t, { style: "animation: anim 100s, anotherAnim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var animations = div.getAnimations();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: []}],
|
|
"records after creation");
|
|
|
|
div.style.animation = "anotherAnim 100s, anim 100s";
|
|
animations = div.getAnimations();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: []}],
|
|
"records after the order is changed");
|
|
|
|
div.style.animation = "anotherAnim 100s, anim 100s";
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "no records after applying the same order");
|
|
}, "animation_order_change");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC,
|
|
iterationComposite: 'replace' });
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.iterationComposite = 'accumulate';
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after iterationComposite is changed");
|
|
|
|
anim.effect.iterationComposite = 'accumulate';
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "no record after setting the same iterationComposite");
|
|
|
|
}, "set_iterationComposite");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC });
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.setKeyframes({ opacity: 0.1 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after keyframes are changed");
|
|
|
|
anim.effect.setKeyframes({ opacity: 0.1 });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "no record after setting the same keyframes");
|
|
|
|
anim.effect.setKeyframes(null);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after keyframes are set to empty");
|
|
|
|
}, "set_keyframes");
|
|
|
|
// Test that starting a single transition that is cancelled by resetting
|
|
// the transition-property property dispatches an added notification and
|
|
// then a removed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "transition: background-color 100s; " +
|
|
"background-color: yellow;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
getComputedStyle(div).transitionProperty;
|
|
div.style.backgroundColor = "lime";
|
|
|
|
// The transition should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after transition start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after transition start");
|
|
|
|
// Cancel the transition by setting transition-property.
|
|
div.style.transitionProperty = "none";
|
|
getComputedStyle(div).transitionProperty;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after transition end");
|
|
}, "single_transition_cancelled_property");
|
|
|
|
// Test that starting a single transition that is cancelled by setting
|
|
// style to the currently animated value dispatches an added
|
|
// notification and then a removed notification.
|
|
test(t => {
|
|
// A long transition with a predictable value.
|
|
var div =
|
|
addDiv(t, { style: "transition: z-index 100s -51s; " +
|
|
"z-index: 10;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
getComputedStyle(div).transitionProperty;
|
|
div.style.zIndex = "100";
|
|
|
|
// The transition should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after transition start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after transition start");
|
|
|
|
// Cancel the transition by setting the current animation value.
|
|
let value = "83";
|
|
assert_equals(getComputedStyle(div).zIndex, value,
|
|
"half-way transition value");
|
|
div.style.zIndex = value;
|
|
getComputedStyle(div).transitionProperty;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after transition end");
|
|
}, "single_transition_cancelled_value");
|
|
|
|
// Test that starting a single transition that is cancelled by setting
|
|
// style to a non-interpolable value dispatches an added notification
|
|
// and then a removed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "transition: line-height 100s; " +
|
|
"line-height: 16px;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
getComputedStyle(div).transitionProperty;
|
|
div.style.lineHeight = "100px";
|
|
|
|
// The transition should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after transition start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after transition start");
|
|
|
|
// Cancel the transition by setting line-height to a non-interpolable value.
|
|
div.style.lineHeight = "normal";
|
|
getComputedStyle(div).transitionProperty;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after transition end");
|
|
}, "single_transition_cancelled_noninterpolable");
|
|
|
|
// Test that starting a single transition and then reversing it
|
|
// dispatches an added notification, then a simultaneous removed and
|
|
// added notification, then a removed notification once finished.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "transition: background-color 100s step-start; " +
|
|
"background-color: yellow;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
getComputedStyle(div).transitionProperty;
|
|
div.style.backgroundColor = "lime";
|
|
|
|
var animations = div.getAnimations();
|
|
|
|
// The transition should cause the creation of a single Animation.
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after transition start");
|
|
|
|
var firstAnimation = animations[0];
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [firstAnimation], changed: [], removed: [] }],
|
|
"records after transition start");
|
|
|
|
firstAnimation.currentTime = 50 * MS_PER_SEC;
|
|
|
|
// Reverse the transition by setting the background-color back to its
|
|
// original value.
|
|
div.style.backgroundColor = "yellow";
|
|
|
|
// The reversal should cause the creation of a new Animation.
|
|
animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after transition reversal");
|
|
|
|
var secondAnimation = animations[0];
|
|
|
|
assert_true(firstAnimation != secondAnimation,
|
|
"second Animation should be different from the first");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [firstAnimation], removed: [] },
|
|
{ added: [secondAnimation], changed: [], removed: [firstAnimation] }],
|
|
"records after transition reversal");
|
|
|
|
// Cancel the transition.
|
|
div.style.transitionProperty = "none";
|
|
getComputedStyle(div).transitionProperty;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [secondAnimation] }],
|
|
"records after transition end");
|
|
}, "single_transition_reversed");
|
|
|
|
// Test that multiple transitions starting and ending on an element
|
|
// at the same time get batched up into a single MutationRecord.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "transition-duration: 100s; " +
|
|
"transition-property: color, background-color, line-height" +
|
|
"background-color: yellow; line-height: 16px" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
getComputedStyle(div).transitionProperty;
|
|
|
|
div.style.backgroundColor = "lime";
|
|
div.style.color = "blue";
|
|
div.style.lineHeight = "24px";
|
|
|
|
// The transitions should cause the creation of three Animations.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 3,
|
|
"getAnimations().length after transition starts");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after transition starts");
|
|
|
|
assert_equals(animations.filter(p => p.playState == "running").length, 3,
|
|
"number of running Animations");
|
|
|
|
// Seek well into each animation.
|
|
animations.forEach(p => p.currentTime = 50 * MS_PER_SEC);
|
|
|
|
// Prepare the set of expected change MutationRecords, one for each
|
|
// animation that was seeked.
|
|
var seekRecords = animations.map(
|
|
p => ({ added: [], changed: [p], removed: [] })
|
|
);
|
|
|
|
// Cancel one of the transitions by setting transition-property.
|
|
div.style.transitionProperty = "background-color, line-height";
|
|
|
|
var colorAnimation = animations.filter(p => p.playState != "running");
|
|
var otherAnimations = animations.filter(p => p.playState == "running");
|
|
|
|
assert_equals(colorAnimation.length, 1,
|
|
"number of non-running Animations after cancelling one");
|
|
assert_equals(otherAnimations.length, 2,
|
|
"number of running Animations after cancelling one");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
seekRecords.concat({ added: [], changed: [], removed: colorAnimation }),
|
|
"records after color transition end");
|
|
|
|
// Cancel the remaining transitions.
|
|
div.style.transitionProperty = "none";
|
|
getComputedStyle(div).transitionProperty;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: otherAnimations }],
|
|
"records after other transition ends");
|
|
}, "multiple_transitions");
|
|
|
|
// Test that starting a single animation that is cancelled by resetting
|
|
// the animation-name property dispatches an added notification and
|
|
// then a removed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Cancel the animation by setting animation-name.
|
|
div.style.animationName = "none";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "single_animation_cancelled_name");
|
|
|
|
// Test that starting a single animation that is cancelled by updating
|
|
// the animation-duration property dispatches an added notification and
|
|
// then a removed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Advance the animation by a second.
|
|
animations[0].currentTime += 1 * MS_PER_SEC;
|
|
|
|
// Cancel the animation by setting animation-duration to a value less
|
|
// than a second.
|
|
div.style.animationDuration = "0.1s";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] },
|
|
{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "single_animation_cancelled_duration");
|
|
|
|
// Test that starting a single animation that is cancelled by updating
|
|
// the animation-delay property dispatches an added notification and
|
|
// then a removed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Cancel the animation by setting animation-delay.
|
|
div.style.animationDelay = "-200s";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "single_animation_cancelled_delay");
|
|
|
|
// Test that starting a single animation that is cancelled by updating
|
|
// the animation-iteration-count property dispatches an added notification
|
|
// and then a removed notification.
|
|
test(t => {
|
|
// A short, repeated animation.
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 0.5s infinite;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Advance the animation until we are past the first iteration.
|
|
animations[0].currentTime += 1 * MS_PER_SEC;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after seeking animations");
|
|
|
|
// Cancel the animation by setting animation-iteration-count.
|
|
div.style.animationIterationCount = "1";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "single_animation_cancelled_iteration_count");
|
|
|
|
// Test that updating an animation property dispatches a changed notification.
|
|
[
|
|
{ name: "duration", prop: "animationDuration", val: "200s" },
|
|
{ name: "timing", prop: "animationTimingFunction", val: "linear" },
|
|
{ name: "iteration", prop: "animationIterationCount", val: "2" },
|
|
{ name: "direction", prop: "animationDirection", val: "reverse" },
|
|
{ name: "state", prop: "animationPlayState", val: "paused" },
|
|
{ name: "delay", prop: "animationDelay", val: "-1s" },
|
|
{ name: "fill", prop: "animationFillMode", val: "both" },
|
|
].forEach(aChangeTest => {
|
|
test(t => {
|
|
// Start a long animation.
|
|
var div = addDiv(t, { style: "animation: anim 100s;" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Change a property of the animation such that it keeps running.
|
|
div.style[aChangeTest.prop] = aChangeTest.val;
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after animation change");
|
|
|
|
// Cancel the animation.
|
|
div.style.animationName = "none";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, `single_animation_change_${aChangeTest.name}`);
|
|
});
|
|
|
|
// Test that calling finish() on a pause-pending (but otherwise finished)
|
|
// animation dispatches a changed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s forwards" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Finish and pause.
|
|
animations[0].finish();
|
|
animations[0].pause();
|
|
assert_true(animations[0].pending && animations[0].playState === "paused",
|
|
"playState after finishing and calling pause()");
|
|
|
|
// Call finish() again to abort the pause
|
|
animations[0].finish();
|
|
assert_equals(animations[0].playState, "finished",
|
|
"playState after finishing again");
|
|
|
|
// Wait for three MutationRecords for the Animation changes to
|
|
// be delivered: one for each finish(), pause(), finish() operation.
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] },
|
|
{ added: [], changed: animations, removed: [] },
|
|
{ added: [], changed: animations, removed: [] }],
|
|
"records after finish(), pause(), finish()");
|
|
|
|
// Cancel the animation.
|
|
div.style = "";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "finish_from_pause_pending");
|
|
|
|
// Test that calling play() on a finished Animation that fills forwards
|
|
// dispatches a changed notification.
|
|
test(t => {
|
|
// Animation with a forwards fill
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s forwards" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Seek to the end
|
|
animations[0].finish();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after finish()");
|
|
|
|
// Since we are filling forwards, calling play() should produce a
|
|
// change record since the animation remains relevant.
|
|
animations[0].play();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after play()");
|
|
|
|
// Cancel the animation.
|
|
div.style = "";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "play_filling_forwards");
|
|
|
|
// Test that calling pause() on an Animation dispatches a changed
|
|
// notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Pause
|
|
animations[0].pause();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after pause()");
|
|
|
|
// Redundant pause
|
|
animations[0].pause();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after redundant pause()");
|
|
|
|
// Cancel the animation.
|
|
div.style = "";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "pause");
|
|
|
|
// Test that calling pause() on an Animation that is pause-pending
|
|
// does not dispatch an additional changed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Pause
|
|
animations[0].pause();
|
|
|
|
// We are now pause-pending, but pause again
|
|
animations[0].pause();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] }],
|
|
"records after pause()");
|
|
|
|
// Cancel the animation.
|
|
div.style = "";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "pause_while_pause_pending");
|
|
|
|
// Test that calling play() on an Animation that is pause-pending
|
|
// dispatches a changed notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Pause
|
|
animations[0].pause();
|
|
|
|
// We are now pause-pending. If we play() now, we will abort the pause
|
|
animations[0].play();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: animations, removed: [] },
|
|
{ added: [], changed: animations, removed: [] }],
|
|
"records after aborting a pause()");
|
|
|
|
// Cancel the animation.
|
|
div.style = "";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "aborted_pause");
|
|
|
|
// Test that calling play() on a finished Animation that does *not* fill
|
|
// forwards dispatches an addition notification.
|
|
test(t => {
|
|
var div =
|
|
addDiv(t, { style: "animation: anim 100s" });
|
|
var observer =
|
|
setupSynchronousObserver(t,
|
|
aOptions.subtree ? div.parentNode : div,
|
|
aOptions.subtree);
|
|
|
|
// The animation should cause the creation of a single Animation.
|
|
var animations = div.getAnimations();
|
|
assert_equals(animations.length, 1,
|
|
"getAnimations().length after animation start");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after animation start");
|
|
|
|
// Seek to the end
|
|
animations[0].finish();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after finish()");
|
|
|
|
// Since we are *not* filling forwards, calling play() is equivalent
|
|
// to creating a new animation since it becomes relevant again.
|
|
animations[0].play();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: animations, changed: [], removed: [] }],
|
|
"records after play()");
|
|
|
|
// Cancel the animation.
|
|
div.style = "";
|
|
getComputedStyle(div).animationName;
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: animations }],
|
|
"records after animation end");
|
|
}, "play_after_finish");
|
|
|
|
});
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer = setupSynchronousObserver(t, div, true);
|
|
|
|
var child = document.createElement("div");
|
|
div.appendChild(child);
|
|
|
|
var anim1 = div.animate({ marginLeft: [ "0px", "50px" ] },
|
|
100 * MS_PER_SEC);
|
|
var anim2 = child.animate({ marginLeft: [ "0px", "100px" ] },
|
|
50 * MS_PER_SEC);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim1], changed: [], removed: [] },
|
|
{ added: [anim2], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
// After setting a new effect, we remove the current animation, anim1,
|
|
// because it is no longer attached to |div|, and then remove the previous
|
|
// animation, anim2. Finally, add back the anim1 which is in effect on
|
|
// |child| now. In addition, we sort them by tree order and they are
|
|
// batched.
|
|
anim1.effect = anim2.effect;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim1] }, // div
|
|
{ added: [anim1], changed: [], removed: [anim2] }], // child
|
|
"records after animation effects are changed");
|
|
}, "set_effect_with_previous_animation");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer = setupSynchronousObserver(t, document, true);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC });
|
|
|
|
var newTarget = document.createElement("div");
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.target = null;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after setting null");
|
|
|
|
anim.effect.target = div;
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after setting a target");
|
|
|
|
anim.effect.target = addDiv(t);
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] },
|
|
{ added: [anim], changed: [], removed: [] }],
|
|
"records after setting a different target");
|
|
}, "set_animation_target");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer = setupSynchronousObserver(t, div, true);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 200 * MS_PER_SEC,
|
|
pseudoElement: '::before' });
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.effect.updateTiming({ duration: 100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [anim], removed: [] }],
|
|
"records after duration is changed");
|
|
|
|
anim.effect.updateTiming({ duration: 100 * MS_PER_SEC });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value");
|
|
|
|
anim.currentTime = anim.effect.getComputedTiming().duration * 2;
|
|
anim.finish();
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after animation end");
|
|
|
|
anim.effect.updateTiming({
|
|
duration: anim.effect.getComputedTiming().duration * 3
|
|
});
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation restarted");
|
|
|
|
anim.effect.updateTiming({ duration: "auto" });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after duration set \"auto\"");
|
|
|
|
anim.effect.updateTiming({ duration: "auto" });
|
|
assert_equals_records(observer.takeRecords(),
|
|
[], "records after assigning same value \"auto\"");
|
|
}, "change_duration_and_currenttime_on_pseudo_elements");
|
|
|
|
test(t => {
|
|
var div = addDiv(t);
|
|
var observer = setupSynchronousObserver(t, div, false);
|
|
|
|
var anim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC });
|
|
var pAnim = div.animate({ opacity: [ 0, 1 ] },
|
|
{ duration: 100 * MS_PER_SEC,
|
|
pseudoElement: "::before" });
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [anim], changed: [], removed: [] }],
|
|
"records after animation is added");
|
|
|
|
anim.finish();
|
|
pAnim.finish();
|
|
|
|
assert_equals_records(observer.takeRecords(),
|
|
[{ added: [], changed: [], removed: [anim] }],
|
|
"records after animation is finished");
|
|
}, "exclude_animations_targeting_pseudo_elements");
|
|
}
|
|
|
|
W3CTest.runner.expectAssertions(0, 12); // bug 1189015
|
|
runTest();
|
|
|
|
</script>
|