mirror of
https://github.com/servo/servo.git
synced 2025-06-29 19:43:39 +01:00
397 lines
9.3 KiB
HTML
397 lines
9.3 KiB
HTML
<!doctype html>
|
|
<meta charset=utf-8>
|
|
<title>Animation.commitStyles</title>
|
|
<link rel="help" href="https://drafts.csswg.org/web-animations/#dom-animation-commitstyles">
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="../../testcommon.js"></script>
|
|
<style>
|
|
.pseudo::before {content: '';}
|
|
.pseudo::after {content: '';}
|
|
.pseudo::marker {content: '';}
|
|
</style>
|
|
<body>
|
|
<div id="log"></div>
|
|
<script>
|
|
'use strict';
|
|
|
|
function assert_numeric_style_equals(opacity, expected, description) {
|
|
return assert_approx_equals(
|
|
parseFloat(opacity),
|
|
expected,
|
|
0.0001,
|
|
description
|
|
);
|
|
}
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.style.opacity = '0.1';
|
|
|
|
const animation = div.animate(
|
|
{ opacity: 0.2 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
|
|
animation.commitStyles();
|
|
|
|
// Cancel the animation so we can inspect the underlying style
|
|
animation.cancel();
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).opacity, 0.2);
|
|
}, 'Commits styles');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
div.style.opacity = '0.1';
|
|
|
|
const animA = div.animate(
|
|
{ opacity: 0.2 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
const animB = div.animate(
|
|
{ opacity: 0.3 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
await animA.finished;
|
|
|
|
animB.cancel();
|
|
|
|
animA.commitStyles();
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).opacity, 0.2);
|
|
}, 'Commits styles for an animation that has been removed');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.style.margin = '10px';
|
|
|
|
const animation = div.animate(
|
|
{ margin: '20px' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
|
|
animation.commitStyles();
|
|
|
|
animation.cancel();
|
|
|
|
assert_equals(div.style.marginLeft, '20px');
|
|
}, 'Commits shorthand styles');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.style.marginLeft = '10px';
|
|
|
|
const animation = div.animate(
|
|
{ marginInlineStart: '20px' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
|
|
animation.commitStyles();
|
|
|
|
animation.cancel();
|
|
|
|
assert_equals(div.style.marginLeft, '20px');
|
|
}, 'Commits logical properties');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.style.marginLeft = '10px';
|
|
|
|
const animation = div.animate({ opacity: [0.2, 0.7] }, 1000);
|
|
animation.currentTime = 500;
|
|
animation.commitStyles();
|
|
animation.cancel();
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).opacity, 0.45);
|
|
}, 'Commits values calculated mid-interval');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.style.setProperty('--target', '0.5');
|
|
|
|
const animation = div.animate(
|
|
{ opacity: 'var(--target)' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
animation.commitStyles();
|
|
animation.cancel();
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
|
|
|
|
// Changes to the variable should have no effect
|
|
div.style.setProperty('--target', '1');
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
|
|
}, 'Commits variables as their computed values');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.style.fontSize = '10px';
|
|
|
|
const animation = div.animate(
|
|
{ width: '10em' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
animation.commitStyles();
|
|
animation.cancel();
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).width, 100);
|
|
|
|
// Changes to the font-size should have no effect
|
|
div.style.fontSize = '20px';
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).width, 100);
|
|
}, 'Commits em units as pixel values');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
div.style.opacity = '0.1';
|
|
|
|
const animA = div.animate(
|
|
{ opacity: '0.2' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
const animB = div.animate(
|
|
{ opacity: '0.2', composite: 'add' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
const animC = div.animate(
|
|
{ opacity: '0.3', composite: 'add' },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
animA.persist();
|
|
animB.persist();
|
|
|
|
await animB.finished;
|
|
|
|
// The values above have been chosen such that various error conditions
|
|
// produce results that all differ from the desired result:
|
|
//
|
|
// Expected result:
|
|
//
|
|
// animA + animB = 0.4
|
|
//
|
|
// Likely error results:
|
|
//
|
|
// <underlying> = 0.1
|
|
// (Commit didn't work at all)
|
|
//
|
|
// animB = 0.2
|
|
// (Didn't add at all when resolving)
|
|
//
|
|
// <underlying> + animB = 0.3
|
|
// (Added to the underlying value instead of lower-priority animations when
|
|
// resolving)
|
|
//
|
|
// <underlying> + animA + animB = 0.5
|
|
// (Didn't respect the composite mode of lower-priority animations)
|
|
//
|
|
// animA + animB + animC = 0.7
|
|
// (Resolved the whole stack, not just up to the target effect)
|
|
//
|
|
|
|
animB.commitStyles();
|
|
|
|
animA.cancel();
|
|
animB.cancel();
|
|
animC.cancel();
|
|
|
|
assert_numeric_style_equals(getComputedStyle(div).opacity, 0.4);
|
|
}, 'Commits the intermediate value of an animation in the middle of stack');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
div.style.opacity = '0.1';
|
|
|
|
// Setup animation
|
|
const animation = div.animate(
|
|
{ opacity: 0.2 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
|
|
// Setup observer
|
|
const mutationRecords = [];
|
|
const observer = new MutationObserver(mutations => {
|
|
mutationRecords.push(...mutations);
|
|
});
|
|
observer.observe(div, { attributes: true, attributeOldValue: true });
|
|
|
|
animation.commitStyles();
|
|
|
|
// Wait for mutation records to be dispatched
|
|
await Promise.resolve();
|
|
|
|
assert_equals(mutationRecords.length, 1, 'Should have one mutation record');
|
|
|
|
const mutation = mutationRecords[0];
|
|
assert_equals(mutation.type, 'attributes');
|
|
assert_equals(mutation.oldValue, 'opacity: 0.1;');
|
|
|
|
observer.disconnect();
|
|
}, 'Triggers mutation observers when updating style');
|
|
|
|
promise_test(async t => {
|
|
const div = createDiv(t);
|
|
div.style.opacity = '0.2';
|
|
|
|
// Setup animation
|
|
const animation = div.animate(
|
|
{ opacity: 0.2 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
animation.finish();
|
|
|
|
// Setup observer
|
|
const mutationRecords = [];
|
|
const observer = new MutationObserver(mutations => {
|
|
mutationRecords.push(...mutations);
|
|
});
|
|
observer.observe(div, { attributes: true });
|
|
|
|
animation.commitStyles();
|
|
|
|
// Wait for mutation records to be dispatched
|
|
await Promise.resolve();
|
|
|
|
assert_equals(mutationRecords.length, 0, 'Should have no mutation records');
|
|
|
|
observer.disconnect();
|
|
}, 'Does NOT trigger mutation observers when the change to style is redundant');
|
|
|
|
test(t => {
|
|
|
|
const div = createDiv(t);
|
|
div.classList.add('pseudo');
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards', pseudoElement: '::before' }
|
|
);
|
|
|
|
assert_throws('NoModificationAllowedError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
}, 'Throws if the target element is a pseudo element');
|
|
|
|
test(t => {
|
|
const animation = createDiv(t).animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
const nonStyleElement
|
|
= document.createElementNS('http://example.org/test', 'test');
|
|
document.body.appendChild(nonStyleElement);
|
|
animation.effect.target = nonStyleElement;
|
|
|
|
assert_throws('NoModificationAllowedError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
|
|
nonStyleElement.remove();
|
|
}, 'Throws if the target element is not something with a style attribute');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
div.style.display = 'none';
|
|
|
|
assert_throws('InvalidStateError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
}, 'Throws if the target effect is display:none');
|
|
|
|
test(t => {
|
|
const container = createDiv(t);
|
|
const div = createDiv(t);
|
|
container.append(div);
|
|
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
container.style.display = 'none';
|
|
|
|
assert_throws('InvalidStateError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
}, "Throws if the target effect's ancestor is display:none");
|
|
|
|
test(t => {
|
|
const container = createDiv(t);
|
|
const div = createDiv(t);
|
|
container.append(div);
|
|
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
container.style.display = 'contents';
|
|
|
|
// Should NOT throw
|
|
animation.commitStyles();
|
|
}, 'Treats display:contents as rendered');
|
|
|
|
test(t => {
|
|
const container = createDiv(t);
|
|
const div = createDiv(t);
|
|
container.append(div);
|
|
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
div.style.display = 'contents';
|
|
container.style.display = 'none';
|
|
|
|
assert_throws('InvalidStateError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
}, 'Treats display:contents in a display:none subtree as not rendered');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards' }
|
|
);
|
|
|
|
div.remove();
|
|
|
|
assert_throws('InvalidStateError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
}, 'Throws if the target effect is disconnected');
|
|
|
|
test(t => {
|
|
const div = createDiv(t);
|
|
div.classList.add('pseudo');
|
|
const animation = div.animate(
|
|
{ opacity: 0 },
|
|
{ duration: 1, fill: 'forwards', pseudoElement: '::before' }
|
|
);
|
|
|
|
div.remove();
|
|
|
|
assert_throws('NoModificationAllowedError', () => {
|
|
animation.commitStyles();
|
|
});
|
|
}, 'Checks the pseudo element condition before the not rendered condition');
|
|
|
|
</script>
|
|
</body>
|