summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html')
-rw-r--r--testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html577
1 files changed, 577 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
new file mode 100644
index 0000000000..9a7dbea8b8
--- /dev/null
+++ b/testing/web-platform/tests/web-animations/interfaces/Animation/commitStyles.html
@@ -0,0 +1,577 @@
+<!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');
+
+test(t => {
+ const div = createDiv(t);
+ div.style.translate = '100px';
+ div.style.rotate = '45deg';
+ div.style.scale = '2';
+
+ const animation = div.animate(
+ { translate: '200px',
+ rotate: '90deg',
+ scale: 3 },
+ { duration: 1, fill: 'forwards' }
+ );
+ animation.finish();
+
+ animation.commitStyles();
+
+ // Cancel the animation so we can inspect the underlying style
+ animation.cancel();
+
+ assert_equals(getComputedStyle(div).translate, '200px');
+ assert_equals(getComputedStyle(div).rotate, '90deg');
+ assert_numeric_style_equals(getComputedStyle(div).scale, 3);
+}, 'Commits styles for individual transform properties');
+
+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(getComputedStyle(div).marginLeft, '20px');
+}, 'Commits logical properties');
+
+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 as physical 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 variable references as their computed values');
+
+
+test(t => {
+ const div = createDiv(t);
+ div.style.setProperty('--target', '0.5');
+ div.style.opacity = 'var(--target)';
+ const animation = div.animate(
+ { '--target': 0.8 },
+ { duration: 1, fill: 'forwards' }
+ );
+ animation.finish();
+ animation.commitStyles();
+ animation.cancel();
+
+ assert_numeric_style_equals(getComputedStyle(div).opacity, 0.8);
+}, 'Commits custom variables');
+
+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);
+
+ div.style.fontSize = '20px';
+ assert_numeric_style_equals(getComputedStyle(div).width, 100,
+ "Changes to the font-size should have no effect");
+}, 'Commits em units as pixel values');
+
+test(t => {
+ const div = createDiv(t);
+ div.style.fontSize = '10px';
+
+ const animation = div.animate(
+ { lineHeight: '1.5' },
+ { duration: 1, fill: 'forwards' }
+ );
+ animation.finish();
+ animation.commitStyles();
+ animation.cancel();
+
+ assert_numeric_style_equals(getComputedStyle(div).lineHeight, 15);
+ assert_equals(div.style.lineHeight, "1.5", "line-height is committed as a relative value");
+
+ div.style.fontSize = '20px';
+ assert_numeric_style_equals(getComputedStyle(div).lineHeight, 30,
+ "Changes to the font-size should affect the committed line-height");
+
+}, 'Commits relative line-height');
+
+test(t => {
+ const div = createDiv(t);
+ const animation = div.animate(
+ { transform: 'translate(20px, 20px)' },
+ { duration: 1, fill: 'forwards' }
+ );
+ animation.finish();
+ animation.commitStyles();
+ animation.cancel();
+ assert_equals(getComputedStyle(div).transform, 'matrix(1, 0, 0, 1, 20, 20)');
+}, 'Commits transforms');
+
+test(t => {
+ const div = createDiv(t);
+ const animation = div.animate(
+ { transform: 'translate(20px, 20px)' },
+ { duration: 1, fill: 'forwards' }
+ );
+ animation.finish();
+ animation.commitStyles();
+ animation.cancel();
+ assert_equals(div.style.transform, 'translate(20px, 20px)');
+}, 'Commits transforms as a transform list');
+
+test(t => {
+ const div = createDiv(t);
+ div.style.width = '200px';
+ div.style.height = '200px';
+
+ const animation = div.animate({ transform: ["translate(100%, 0%)", "scale(3)"] }, 1000);
+ animation.currentTime = 500;
+ animation.commitStyles();
+ animation.cancel();
+
+ // TODO(https://github.com/w3c/csswg-drafts/issues/2854):
+ // We can't check the committed value directly since it is not specced yet in this case,
+ // but it should still produce the correct resolved value.
+ assert_equals(getComputedStyle(div).transform, "matrix(2, 0, 0, 2, 100, 0)",
+ "Resolved transform is correct after commit.");
+}, 'Commits matrix-interpolated relative transforms');
+
+test(t => {
+ const div = createDiv(t);
+ div.style.width = '200px';
+ div.style.height = '200px';
+
+ const animation = div.animate({ transform: ["none", "none"] }, 1000);
+ animation.currentTime = 500;
+ animation.commitStyles();
+ animation.cancel();
+
+ assert_equals(div.style.transform, "none",
+ "Resolved transform is correct after commit.");
+}, 'Commits "none" transform');
+
+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';
+
+ const animA = div.animate(
+ { opacity: '0.2', composite: 'add' },
+ { 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 error cases are similar to the above test with one additional case;
+ // verifying that the animations composite on top of the correct underlying
+ // base style.
+ //
+ // Expected result:
+ //
+ // <underlying> + animA + animB = 0.5
+ //
+ // Additional error results:
+ //
+ // <underlying> + animA + animB + animC + animA + animB = 1.0 (saturates)
+ // (Added to the computed value instead of underlying value when
+ // resolving)
+ //
+ // animA + animB = 0.4
+ // Failed to composite on top of underlying value.
+ //
+
+ animB.commitStyles();
+
+ animA.cancel();
+ animB.cancel();
+ animC.cancel();
+
+ assert_numeric_style_equals(getComputedStyle(div).opacity, 0.5);
+}, 'Commit composites on top of the underlying value');
+
+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_dom('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_dom('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_dom('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_dom('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_dom('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_dom('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_dom('NoModificationAllowedError', () => {
+ animation.commitStyles();
+ });
+}, 'Checks the pseudo element condition before the not rendered condition');
+
+</script>
+</body>