[scroll-animations] Determine forced updates using style roots

For forced style updates on a single element (e.g. getComputedStyle),
we try to avoid updating the style and layout-tree if we know that
the next call to UpdateStyleAndLayoutTree will not affect that element.
Figuring this out involves traversing the ancestor chain of the target
element, looking for dirty-flags and the like.

However, with the introduction of scroll-timelines, it is no longer
sufficient to traverse the ancestor chain, because the named timeline
lookup can reach previous siblings (and previous siblings of all
ancestors). We probably don't want to traverse all of those siblings,
so instead this CL suggests basing the optimization on whether or not
there is a style recalc/invalidation root in the ancestor chain.
This makes it possible to solve the problem of scroll-timelines/
siblings by checking the parent element of the style root against the
the parent of the target element.

Bug: 1317765
Change-Id: Ic13587ce7535da02aeb00699e29c26ee867eb249
Fixed: 1356493
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3859628
Reviewed-by: Rune Lillesveen <futhark@chromium.org>
Commit-Queue: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1043196}
diff --git a/scroll-animations/css/scroll-timeline-sibling-gcs.html b/scroll-animations/css/scroll-timeline-sibling-gcs.html
new file mode 100644
index 0000000..21d5b8c
--- /dev/null
+++ b/scroll-animations/css/scroll-timeline-sibling-gcs.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>scroll-timeline and container queries</title>
+<link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#scroll-timeline-shorthand">
+<link rel="help" src="https://drafts.csswg.org/css-contain-3/#container-queries">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/web-animations/testcommon.js"></script>
+<style>
+  #scroller {
+    overflow: auto;
+    width: auto;
+    height: 100px;
+  }
+  #scroller > div {
+    height: 200px;
+  }
+
+  @keyframes anim {
+    from { background-color: rgb(100, 100, 100); }
+    to { background-color: rgb(200, 200, 200); }
+  }
+  #element {
+    height: 10px;
+    width: 10px;
+    animation: anim 10s linear timeline;
+    background-color: rgb(0, 0, 0);
+  }
+</style>
+<div>
+  <div id=scroller>
+    <div></div>
+  </div>
+  <div>
+    <div id=element></div>
+  </div>
+</div>
+<script>
+  promise_test(async (t) => {
+    element.offsetTop;
+    scroller.scrollTop = 50;
+    await waitForNextFrame();
+    // Unknown timeline, time held at zero.
+    assert_equals(getComputedStyle(element).backgroundColor, 'rgb(100, 100, 100)');
+    scroller.style.scrollTimeline = 'timeline';
+    assert_equals(getComputedStyle(element).backgroundColor, 'rgb(150, 150, 150)');
+  }, 'Timelines appearing on preceding siblings are visible to getComputedStyle');
+</script>