commit 9c5ef3f5e111c199ec1a3a92a398e951831e7c6f
parent d8dd96fc8c49b21643aeeeeab790abe56920fb9c
Author: David Awogbemila <awogbemila@chromium.org>
Date: Wed, 7 Jan 2026 09:19:17 +0000
Bug 2008570 [wpt PR 56989] - [animation-trigger] Don't update attachments if layout is needed, a=testonly
Automatic update from web-platform-tests
[animation-trigger] Don't update attachments if layout is needed
This patch address a few issues that affected the linked bug:
1. For a triggered animation, when a related property, such as
timeline-trigger, changes on a different element, we might fail to
queue the animation up for a trigger attachment update, because, as
far as it knows, the animating element's style didn't change.
2. It can happen that we run style and note that we have a pending
layout update (due to a timeline-trigger change) but we then skip[1]
the layout update. Performing the trigger attachments updates in
this scenario causes us to attach animations to triggers based on
stale information.
To address 1., this patch has DocumentAnimations::triggered_animations_
consistently track animations whose styles declare trigger attachments
per the most recent style update.
To address 2., this patch avoids the trigger attachment update if we
know that we have a pending layout.
[1]
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/document.cc;l=2627?q=document::updatestyleandlayouttree&ss=chromium%2Fchromium%2Fsrc
Bug: 467869217
Change-Id: I3c94efb90e7a9338afe2173f0aa57e81016ed8d2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7265794
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Reviewed-by: David Awogbemila <awogbemila@chromium.org>
Commit-Queue: David Awogbemila <awogbemila@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1564397}
--
wpt-commits: c5cdba7fe97355a4058edf80c3b71336f3c11340
wpt-pr: 56989
Diffstat:
1 file changed, 145 insertions(+), 0 deletions(-)
diff --git a/testing/web-platform/tests/scroll-animations/animation-trigger/timeline-trigger-toggle.tentative.html b/testing/web-platform/tests/scroll-animations/animation-trigger/timeline-trigger-toggle.tentative.html
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <link rel="help" src="https://drafts.csswg.org/css-animations-2/#animation-trigger">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/web-animations/testcommon.js"></script>
+ <script src="/dom/events/scrolling/scroll_support.js"></script>
+ <script src="support/support.js"></script>
+</head>
+
+<body>
+ <style>
+ @keyframes expand {
+ from {
+ width: 100px;
+ }
+
+ to {
+ width: 100px;
+ }
+ }
+
+ #scroller {
+ width: 300px;
+ height: 300px;
+ overflow: scroll;
+ }
+
+ #source {
+ height: 100px;
+ width: 100px;
+ background-color: seagreen;
+ }
+
+ .trigger-source {
+ timeline-trigger: --trigger view() contain;
+ }
+
+ .blob {
+ height: 50px;
+ background-color: lightgrey;
+ }
+
+ .blob:nth-child(even) {
+ background-color: darkgrey;
+ }
+
+ #target {
+ background-color: tomato;
+ position: absolute;
+ animation: expand linear 1s both;
+ animation-trigger: --trigger play-forwards play-backwards;
+ width: 50px;
+ }
+ </style>
+ <div id=scroller>
+ <div class=blob></div>
+ <div class=blob></div>
+ <div id=source>Source</div>
+ <div id=target>Target</div>
+ <div class=blob></div>
+ <div class=blob></div>
+ <div class=blob></div>
+ <div class=blob></div>
+ <div class=blob></div>
+ <div class=blob></div>
+ </div>
+ <hr>
+ <button id=button>Toggle timeline-trigger</button>
+ <pre id=pre></pre>
+ <hr>
+ <script>
+ const scroller = document.getElementById("scroller");
+ const source = document.getElementById("source");
+ const target = document.getElementById("target");
+ const animation = target.getAnimations()[0];
+
+ async function scrollSourceOutOfView() {
+ // First, scroll source to the top of the viewport.
+ let scrollend_promise =
+ waitForScrollEndFallbackToDelayWithoutScrollEvent(scroller);
+ source.scrollIntoView({block: "start"});
+ await scrollend_promise;
+ // Now scroll a litte further to push it just out of view.
+ scrollend_promise =
+ waitForScrollEndFallbackToDelayWithoutScrollEvent(scroller);
+ scroller.scrollBy({top: 50});
+ await scrollend_promise;
+ await waitForCompositorCommit();
+ }
+
+ async function scrollSourceIntoView() {
+ let scrollend_promise =
+ waitForScrollEndFallbackToDelayWithoutScrollEvent(scroller);
+ source.scrollIntoView({block: "center"});
+ await scrollend_promise;
+ await waitForCompositorCommit();
+ }
+
+ promise_test(async(test) => {
+ assert_equals(getComputedStyle(source).timelineTrigger, "none");
+ assert_equals(animation.playState, "paused");
+
+ // Toggle the trigger on.
+ source.style.timelineTrigger = "--trigger view() contain";
+ assert_equals(getComputedStyle(source).timelineTrigger,
+ "--trigger view() contain");
+ await waitForCompositorCommit();
+
+ // Animation should be playing forwards as source is already in view.
+ assert_equals(animation.playState, "running");
+
+ // Simulate the animation finishing (instead of stalling the test).
+ animation.finish();
+ assert_equals(animation.playState, "finished");
+
+ await scrollSourceOutOfView();
+ // Animation should be playing backwards.
+ assert_equals(animation.playState, "running");
+
+ // Simulate the backwards finish.
+ animation.finish();
+ assert_equals(animation.playState, "finished");
+ assert_equals(animation.currentTime, 0);
+
+ // Toggle the trigger off.
+ source.style.timelineTrigger = "initial";
+ assert_equals(getComputedStyle(source).timelineTrigger, "none");
+ await waitForCompositorCommit();
+
+ // There should be no change to the animation.
+ assert_equals(animation.playState, "finished");
+
+ await scrollSourceIntoView();
+
+ // There should still be no change to the animation.
+ assert_equals(animation.playState, "finished");
+ assert_equals(animation.currentTime, 0);
+ }, "Toggling trigger on starts controlling attached animation; toggling " +
+ "off stops");
+ </script>
+</body>
+</html>