Consecutive touch scrolling should generate only one scrollend event

Touch scroll, pause, touch scroll again and we should receive
only one scrollend event.
Add waitForCompositorReady() which runs an opacity animation before
doing the actual test, so that we are sure the compositor thread is ready.
If we don't wait for the compositor, we'll have flaky tests. Before we start each test we reset the scrolling state, and one way of making sure that the state have been applied is to wait for the compositor commit. In this case we are using a 1ms opacity animation that forces the commit (because it runs on the compositor) and after that we are sure everything is in its initial state.

Bug: 1400447
Change-Id: I3ca62a01e947229e4698f934f0f2d441e656a424
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4191397
Commit-Queue: Mehdi Kazemi <mehdika@chromium.org>
Reviewed-by: Kevin Ellis <kevers@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1097449}
diff --git a/dom/events/scrolling/scroll_support.js b/dom/events/scrolling/scroll_support.js
index 847a989..de23b6f 100644
--- a/dom/events/scrolling/scroll_support.js
+++ b/dom/events/scrolling/scroll_support.js
@@ -58,6 +58,17 @@
     });
   });
 }
+
+// Please don't remove this. This is necessary for chromium-based browsers.
+// This shouldn't be necessary if the test harness deferred running the tests
+// until after paint holding. This can be a no-op on user-agents that do not
+// have a separate compositor thread.
+async function waitForCompositorReady() {
+  const animation =
+      document.body.animate({ opacity: [ 1, 1 ] }, {duration: 1 });
+  return animation.finished;
+}
+
 function waitForNextFrame() {
   const startTime = performance.now();
   return new Promise(resolve => {
@@ -71,7 +82,6 @@
   });
 }
 
-
 // TODO(crbug.com/1400399): Deprecate as frame rates may vary greatly in
 // different test environments.
 function waitForAnimationEnd(getValue) {
diff --git a/dom/events/scrolling/scrollend-event-for-user-scroll.html b/dom/events/scrolling/scrollend-event-for-user-scroll.html
index e9be7c9..d51472f 100644
--- a/dom/events/scrolling/scrollend-event-for-user-scroll.html
+++ b/dom/events/scrolling/scrollend-event-for-user-scroll.html
@@ -45,8 +45,8 @@
   window.addEventListener('scrollend', callback);
 }
 
-async function createScrollendPromise(test) {
-  return waitForScrollendEvent(test, target_div).then(evt => {
+async function createScrollendPromise(test, timeoutMs = 500) {
+  return waitForScrollendEvent(test, target_div, timeoutMs).then(evt => {
       assert_false(evt.cancelable, 'Event is not cancelable');
       assert_false(evt.bubbles, 'Event targeting element does not bubble');
   });
@@ -60,11 +60,21 @@
       return;
 
     await resetTargetScrollState(t);
-    await waitForCompositorCommit();
+    await waitForCompositorReady();
 
-    const targetScrollendPromise = createScrollendPromise(t);
+    const timeout = 1000; // Because we have two pauses we need longer timeout
+    const targetScrollendPromise = createScrollendPromise(t, timeout);
     verifyNoScrollendOnDocument(t);
 
+    let scrollend_count = 0;
+    const scrollend_listener = () => {
+      scrollend_count += 1;
+    };
+    target_div.addEventListener("scrollend", scrollend_listener);
+    t.add_cleanup(() => {
+      target_div.removeEventListener('scrollend', scrollend_listener);
+    });
+
     // Perform a touch drag on target div and wait for target_div to get
     // a scrollend event.
     await new test_driver.Actions()
@@ -75,6 +85,9 @@
         .pointerMove(0, -40, {origin: target_div}) //  Drag up to move down.
         .addTick()
         .pause(200) //  Prevent inertial scroll.
+        .pointerMove(0, -60, {origin: target_div})
+        .addTick()
+        .pause(200) //  Prevent inertial scroll.
         .pointerUp()
         .send();
 
@@ -82,6 +95,7 @@
 
     assert_true(target_div.scrollTop > 0);
     await verifyScrollStopped(t, target_div);
+    assert_equals(scrollend_count, 1);
   }, 'Tests that the target_div gets scrollend event when touch dragging.');
 
   promise_test(async (t) => {
@@ -92,7 +106,7 @@
       return;
 
     await resetTargetScrollState(t);
-    await waitForCompositorCommit();
+    await waitForCompositorReady();
 
     const targetScrollendPromise = createScrollendPromise(t);
     verifyNoScrollendOnDocument(t);
@@ -122,7 +136,9 @@
     if (scrollbar_width == 0)
       return;
 
-    resetTargetScrollState(t);
+    await resetTargetScrollState(t);
+    await waitForCompositorReady();
+
     const targetScrollendPromise = createScrollendPromise(t);
     verifyNoScrollendOnDocument(t);
 
@@ -146,7 +162,9 @@
       'scrollbar thumb.');
 
   promise_test(async (t) => {
-    resetTargetScrollState(t);
+    await resetTargetScrollState(t);
+    await waitForCompositorReady();
+
     const targetScrollendPromise = createScrollendPromise(t);
     verifyNoScrollendOnDocument(t);
 
@@ -167,7 +185,7 @@
 
   promise_test(async (t) => {
     await resetTargetScrollState(t);
-    await waitForCompositorCommit();
+    await waitForCompositorReady();
 
     verifyNoScrollendOnDocument(t);
     const targetScrollendPromise = createScrollendPromise(t);