commit 57441af46bd3a4417288982901b39bc6365365d3
parent 58db02e18eb31eeb8e60027a4753f9b35213ec71
Author: Morten Stenshorne <mstensho@chromium.org>
Date: Wed, 19 Nov 2025 04:56:08 +0000
Bug 2000771 [wpt PR 56080] - [anchor] Request main frame animation if anchors animate transforms., a=testonly
Automatic update from web-platform-tests
[anchor] Request main frame animation if anchors animate transforms.
If transforms on an anchor affects layout of its anchored elements,
animations cannot run on the compositor thread without involving the
main thread. Without this fix, if a transform change on an anchor was
animated, we'd rely on main frame animations being triggered by
something else (e.g. by moving the mouse cursor), which was bad for
correctness. With this fix, such animations will correctly cause
anchored elements to be relaid out during the animation, although this
layout may be behind by a frame or two.
Bug: 382294252
Change-Id: Ib6c94077ed44ada7d5f1afd7bbda6f4e774ed9ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7123703
Reviewed-by: Robert Flack <flackr@chromium.org>
Commit-Queue: Morten Stenshorne <mstensho@chromium.org>
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
Reviewed-by: Stefan Zager <szager@chromium.org>
Reviewed-by: Ian Kilpatrick <ikilpatrick@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1546313}
--
wpt-commits: 384f438c011fc68f333c6acb48f077d3eec1d515
wpt-pr: 56080
Diffstat:
6 files changed, 337 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/css/css-anchor-position/transform-010.html b/testing/web-platform/tests/css/css-anchor-position/transform-010.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Animated anchor transform</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining-position">
+<style>
+ #anchor {
+ anchor-name: --a;
+ width: 200px;
+ height: 200px;
+ transform: translateX(0);
+ background: hotpink;
+ }
+ #anchored {
+ position: absolute;
+ position-anchor: --a;
+ position-area: bottom right;
+ width: 100%;
+ height: 100%;
+ background: cyan;
+ }
+ @keyframes anim {
+ from { transform: translateX(0) }
+ to { transform: translateX(200px) }
+ }
+</style>
+<div style="contain:layout; width:400px; height:400px;">
+ <div id="anchor"></div>
+ <div id="anchored"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const done = Promise.withResolvers();
+ let got_halfway = false;
+ const observer = new ResizeObserver((entries)=> {
+ assert_equals(entries.length, 1);
+ const inlineSize = entries[0].contentBoxSize[0].inlineSize;
+ if (inlineSize > 30 && inlineSize < 170) {
+ got_halfway = true;
+ done.resolve();
+ }
+ });
+ anchor.onanimationend = ()=> { done.reject(); }
+
+ promise_test(async t => {
+ anchor.style.animation = "2000ms linear anim";
+ observer.observe(anchored);
+ try { await done.promise; } catch(e) {}
+ assert_true(got_halfway, "animation frame somewhere in the middle");
+ }, "Animation being updated");
+</script>
diff --git a/testing/web-platform/tests/css/css-anchor-position/transform-011.html b/testing/web-platform/tests/css/css-anchor-position/transform-011.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Animated anchor transform</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining-position">
+<style>
+ #anchor {
+ anchor-name: --a;
+ width: 200px;
+ height: 200px;
+ rotate: 30deg;
+ background: hotpink;
+ }
+ #anchored {
+ position: absolute;
+ position-anchor: --a;
+ position-area: bottom right;
+ width: 100%;
+ height: 100%;
+ background: cyan;
+ }
+ @keyframes anim {
+ from { rotate: 30deg; }
+ to { rotate: 0deg; }
+ }
+</style>
+<div style="contain:layout; width:300px; height:400px;">
+ <div id="anchor"></div>
+ <div id="anchored"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const done = Promise.withResolvers();
+ let got_halfway = false;
+ const observer = new ResizeObserver((entries)=> {
+ assert_equals(entries.length, 1);
+ const inlineSize = entries[0].contentBoxSize[0].inlineSize;
+ if (inlineSize > 66 && inlineSize < 94) {
+ got_halfway = true;
+ done.resolve();
+ }
+ });
+ anchor.onanimationend = ()=> { done.reject(); }
+
+ promise_test(async t => {
+ anchor.style.animation = "2000ms linear anim";
+ observer.observe(anchored);
+ try { await done.promise; } catch(e) {}
+ assert_true(got_halfway, "animation frame somewhere in the middle");
+ }, "Animation being updated");
+</script>
diff --git a/testing/web-platform/tests/css/css-anchor-position/transform-012.html b/testing/web-platform/tests/css/css-anchor-position/transform-012.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Animated anchor transform</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining-position">
+<style>
+ #anchor {
+ anchor-name: --a;
+ width: 200px;
+ height: 200px;
+ scale: 0.5;
+ background: hotpink;
+ }
+ #anchored {
+ position: absolute;
+ position-anchor: --a;
+ position-area: bottom right;
+ width: 100%;
+ height: 100%;
+ background: cyan;
+ }
+ @keyframes anim {
+ from { scale: 0.5; }
+ to { scale: 2; }
+ }
+</style>
+<div style="contain:layout; width:300px; height:400px;">
+ <div id="anchor"></div>
+ <div id="anchored"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const done = Promise.withResolvers();
+ let got_halfway = false;
+ const observer = new ResizeObserver((entries)=> {
+ assert_equals(entries.length, 1);
+ const inlineSize = entries[0].contentBoxSize[0].inlineSize;
+ if (inlineSize > 40 && inlineSize < 110) {
+ got_halfway = true;
+ done.resolve();
+ }
+ });
+ anchor.onanimationend = ()=> { done.reject(); }
+
+ promise_test(async t => {
+ anchor.style.animation = "2000ms linear anim";
+ observer.observe(anchored);
+ try { await done.promise; } catch(e) {}
+ assert_true(got_halfway, "animation frame somewhere in the middle");
+ }, "Animation being updated");
+</script>
diff --git a/testing/web-platform/tests/css/css-anchor-position/transform-013.html b/testing/web-platform/tests/css/css-anchor-position/transform-013.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Animated anchor transform</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining-position">
+<style>
+ #anchor {
+ anchor-name: --a;
+ width: 200px;
+ height: 200px;
+ translate: 0;
+ background: hotpink;
+ }
+ #anchored {
+ position: absolute;
+ position-anchor: --a;
+ position-area: bottom right;
+ width: 100%;
+ height: 100%;
+ background: cyan;
+ }
+ @keyframes anim {
+ from { translate: 0; }
+ to { translate: 100px; }
+ }
+</style>
+<div style="contain:layout; width:300px; height:400px;">
+ <div id="anchor"></div>
+ <div id="anchored"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const done = Promise.withResolvers();
+ let got_halfway = false;
+ const observer = new ResizeObserver((entries)=> {
+ assert_equals(entries.length, 1);
+ const inlineSize = entries[0].contentBoxSize[0].inlineSize;
+ if (inlineSize > 30 && inlineSize < 70) {
+ got_halfway = true;
+ done.resolve();
+ }
+ });
+ anchor.onanimationend = ()=> { done.reject(); }
+
+ promise_test(async t => {
+ anchor.style.animation = "3000ms linear anim";
+ observer.observe(anchored);
+ try { await done.promise; } catch(e) {}
+ assert_true(got_halfway, "animation frame somewhere in the middle");
+ }, "Animation being updated");
+</script>
diff --git a/testing/web-platform/tests/css/css-anchor-position/transform-014.html b/testing/web-platform/tests/css/css-anchor-position/transform-014.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Animated anchor transform in iframe</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining-position">
+<iframe id="iframe" style="width:300px; height:400px;" srcdoc='
+<style>
+ #anchor {
+ anchor-name: --a;
+ width: 200px;
+ height: 200px;
+ translate: 0;
+ background: hotpink;
+ }
+ #anchored {
+ position: absolute;
+ position-anchor: --a;
+ position-area: bottom right;
+ width: 100%;
+ height: 100%;
+ background: cyan;
+ }
+ @keyframes anim {
+ from { translate: 0; }
+ to { translate: 100px; }
+ }
+ body { margin: 0; }
+</style>
+<div id="anchor"></div>
+<div id="anchored"></div>
+<script>
+ const done = Promise.withResolvers();
+ let got_halfway = false;
+ const observer = new ResizeObserver((entries)=> {
+ const inlineSize = entries[0].contentBoxSize[0].inlineSize;
+ if (inlineSize > 30 && inlineSize < 90) {
+ got_halfway = true;
+ done.resolve();
+ }
+ });
+ anchor.onanimationend = ()=> { done.reject(); }
+
+ async function runTest() {
+ await new Promise(resolve => { window.onmessage = resolve; });
+ anchor.style.animation = "2000ms linear anim";
+ try {
+ observer.observe(anchored);
+ await done.promise;
+ observer.unobserve(anchored);
+ anchor.style.animation = "none";
+ } catch (e) {}
+ window.top.postMessage(got_halfway);
+ }
+ runTest();
+</script>
+'></iframe>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ promise_test(async t => {
+ await new Promise(resolve => { window.onload = resolve; });
+ iframe.contentWindow.postMessage("go");
+ let success = await new Promise(resolve => {
+ window.onmessage = function(e) { resolve(e.data); };
+ });
+ assert_true(success, "animation frame somewhere in the middle");
+ }, "Animation being updated");
+</script>
diff --git a/testing/web-platform/tests/css/css-anchor-position/transform-015.html b/testing/web-platform/tests/css/css-anchor-position/transform-015.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Animated transform on anchor ancestor</title>
+<link rel="author" title="Morten Stenshorne" href="mailto:mstensho@chromium.org">
+<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#determining-position">
+<style>
+ #anchor {
+ anchor-name: --a;
+ width: 200px;
+ height: 200px;
+ background: hotpink;
+ }
+ #transformed {
+ width: 500px;
+ transform: translateX(0);
+ background: yellow;
+ }
+ #anchored {
+ position: absolute;
+ position-anchor: --a;
+ position-area: bottom right;
+ width: 100%;
+ height: 100%;
+ background: cyan;
+ }
+ @keyframes anim {
+ from { transform: translateX(0) }
+ to { transform: translateX(200px) }
+ }
+</style>
+<div style="contain:layout; width:400px; height:400px;">
+ <div>
+ <div id="transformed">
+ <div>
+ <div id="anchor"></div>
+ </div>
+ </div>
+ </div>
+ <div id="anchored"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ const done = Promise.withResolvers();
+ let got_halfway = false;
+ const observer = new ResizeObserver((entries)=> {
+ assert_equals(entries.length, 1);
+ const inlineSize = entries[0].contentBoxSize[0].inlineSize;
+ if (inlineSize > 30 && inlineSize < 170) {
+ got_halfway = true;
+ done.resolve();
+ }
+ });
+ anchor.onanimationend = ()=> { done.reject(); }
+
+ promise_test(async t => {
+ transformed.style.animation = "2000ms linear anim";
+ observer.observe(anchored);
+ try { await done.promise; } catch(e) {}
+ assert_true(got_halfway, "animation frame somewhere in the middle");
+ }, "Animation being updated");
+</script>