Add some more scroll-latching tests
https://bugs.webkit.org/show_bug.cgi?id=209924

Reviewed by Zalan Bujtas.

Add three scroll latching tests.

iframe-latch-small-deltas.html tests that starting a second scroll where the dominant
direction can't be determined re-uses the previous latching. This test currently fails.

overflow-in-iframe-latching.html tests a wheel over a scrolled-to-top overflow inside
a not-scrolled-to-top iframe; the iframe should scroll. This test currently fails.

latching-and-wheel-events.html tests that wheel events always go to the innermost target,
even if not latched, and don't propagate across frame boundaries. This test passes
the event propagation part, but fails in a similar way to overflow-in-iframe-latching.html .

* fast/scrolling/latching/iframe-latch-small-deltas-expected.txt: Added.
* fast/scrolling/latching/iframe-latch-small-deltas.html: Added.
* fast/scrolling/latching/latching-and-wheel-events-expected.txt: Added.
* fast/scrolling/latching/latching-and-wheel-events.html: Added.
* fast/scrolling/latching/overflow-in-iframe-latching-expected.txt: Added.
* fast/scrolling/latching/overflow-in-iframe-latching.html: Added.
* platform/mac-wk1/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt: Added.
* platform/mac-wk1/fast/scrolling/latching/latching-and-wheel-events-expected.txt: Added.
* platform/mac-wk1/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt: Added.
* resources/ui-helper.js:
(window.UIHelper.async mouseWheelScrollAt):
(window.UIHelper.async waitForScrollCompletion):

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@259417 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index c1a5cae..2bac8be 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,35 @@
+2020-04-02  Simon Fraser  <simon.fraser@apple.com>
+
+        Add some more scroll-latching tests
+        https://bugs.webkit.org/show_bug.cgi?id=209924
+
+        Reviewed by Zalan Bujtas.
+        
+        Add three scroll latching tests.
+        
+        iframe-latch-small-deltas.html tests that starting a second scroll where the dominant
+        direction can't be determined re-uses the previous latching. This test currently fails.
+        
+        overflow-in-iframe-latching.html tests a wheel over a scrolled-to-top overflow inside
+        a not-scrolled-to-top iframe; the iframe should scroll. This test currently fails.
+        
+        latching-and-wheel-events.html tests that wheel events always go to the innermost target,
+        even if not latched, and don't propagate across frame boundaries. This test passes
+        the event propagation part, but fails in a similar way to overflow-in-iframe-latching.html .
+
+        * fast/scrolling/latching/iframe-latch-small-deltas-expected.txt: Added.
+        * fast/scrolling/latching/iframe-latch-small-deltas.html: Added.
+        * fast/scrolling/latching/latching-and-wheel-events-expected.txt: Added.
+        * fast/scrolling/latching/latching-and-wheel-events.html: Added.
+        * fast/scrolling/latching/overflow-in-iframe-latching-expected.txt: Added.
+        * fast/scrolling/latching/overflow-in-iframe-latching.html: Added.
+        * platform/mac-wk1/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt: Added.
+        * platform/mac-wk1/fast/scrolling/latching/latching-and-wheel-events-expected.txt: Added.
+        * platform/mac-wk1/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt: Added.
+        * resources/ui-helper.js:
+        (window.UIHelper.async mouseWheelScrollAt):
+        (window.UIHelper.async waitForScrollCompletion):
+
 2020-04-02  Daniel Bates  <dabates@apple.com>
 
         Traverse float descendants when computing event and touch-action regions
diff --git a/LayoutTests/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt b/LayoutTests/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt
new file mode 100644
index 0000000..06768a9
--- /dev/null
+++ b/LayoutTests/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt
@@ -0,0 +1,20 @@
+
+Tests that a second scroll with small x/y deltas uses latching from an earlier scroll.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS iframeTarget.contentWindow.pageYOffset is 0
+PASS window.pageYOffset is 200
+After scroll
+PASS iframeTarget.contentWindow.pageYOffset is 500
+PASS window.pageYOffset is 200
+After wait
+PASS iframeTarget.contentWindow.pageYOffset is 500
+PASS window.pageYOffset is 200
+FAIL iframeTarget.contentWindow.pageYOffset should be 480. Was 500.
+FAIL window.pageYOffset should be 200. Was 180.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/scrolling/latching/iframe-latch-small-deltas.html b/LayoutTests/fast/scrolling/latching/iframe-latch-small-deltas.html
new file mode 100644
index 0000000..91cbce4
--- /dev/null
+++ b/LayoutTests/fast/scrolling/latching/iframe-latch-small-deltas.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        body {
+            height: 1000px;
+        }
+    </style>
+    <script src="../../../resources/js-test-pre.js"></script>
+    <script src="../../../resources/ui-helper.js"></script>
+    <script>
+        jsTestIsAsync = true;
+
+        var iframeTarget;
+
+        async function scrollTest()
+        {
+            window.scrollTo(0, 200);
+
+            iframeTarget = document.getElementById('target');
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '0');
+            shouldBe('window.pageYOffset', '200');
+
+            if (!window.eventSender) {
+                finishJSTest();
+                return;
+            }
+
+            eventSender.mouseMoveTo(50, 250); // Inside the iframe.
+            eventSender.monitorWheelEvents();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, 'began', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, 'ended', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'none', 'begin');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'none', 'continue');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'none', 'continue');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'none', 'continue');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, 'none', 'end');
+            await UIHelper.waitForScrollCompletion();
+
+            debug('After scroll');
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '500');
+            shouldBe('window.pageYOffset', '200');
+
+            await UIHelper.animationFrame();
+
+            debug('After wait');
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '500');
+            shouldBe('window.pageYOffset', '200');
+
+            eventSender.monitorWheelEvents();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, 'began', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(-1, 1, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 1, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, 'ended', 'none');
+
+            await UIHelper.waitForScrollCompletion();
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '480');
+            shouldBe('window.pageYOffset', '200');
+
+            finishJSTest();
+        }
+
+        function setupTopLevel() 
+        {
+            description("Tests that a second scroll with small x/y deltas uses latching from an earlier scroll.");
+            setTimeout(scrollTest, 0);
+        }
+        
+    </script>
+
+</head>
+<body>
+<div id="parent" style="height: 2000px">
+    <iframe id="target" name="target" style="border:solid 1px green; height: 500px; width: 500px;" 
+     srcdoc= "
+        <style>
+            body {
+                height: 1000px;
+                margin: 0;
+                background-image: repeating-linear-gradient(silver, white 200px);
+            }
+        </style>
+        <body></body>
+     "
+     onload="setupTopLevel();">
+    </iframe>
+</div>
+<div id="console"></div>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/scrolling/latching/latching-and-wheel-events-expected.txt b/LayoutTests/fast/scrolling/latching/latching-and-wheel-events-expected.txt
new file mode 100644
index 0000000..62e2cdb
--- /dev/null
+++ b/LayoutTests/fast/scrolling/latching/latching-and-wheel-events-expected.txt
@@ -0,0 +1,24 @@
+
+Tests that wheel events still go to the innermost scroller even if latching skips it. Also tests that wheel events don't propagate between frames.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Start with main frame scrolled, iframe scrolled, overflow scrolled to top
+PASS iframeTarget.contentWindow.pageYOffset is 100
+PASS overflowInIframeElement.scrollTop is 0
+PASS window.pageYOffset is 20
+PASS mainFrameWheelEventCount is 0
+PASS iframeWindowWheelEventCount is 0
+PASS overflowInIframeWheelEventCount is 0
+After scroll
+FAIL iframeTarget.contentWindow.pageYOffset should be 0. Was 100.
+FAIL overflowInIframeElement.scrollTop should be 0. Was 200.
+PASS window.pageYOffset is 20
+PASS mainFrameWheelEventCount is 0
+PASS iframeWindowWheelEventCount is 2
+PASS overflowInIframeWheelEventCount is 2
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/scrolling/latching/latching-and-wheel-events.html b/LayoutTests/fast/scrolling/latching/latching-and-wheel-events.html
new file mode 100644
index 0000000..8546dfe
--- /dev/null
+++ b/LayoutTests/fast/scrolling/latching/latching-and-wheel-events.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        body {
+            height: 2000px;
+        }
+
+        iframe {
+            width: 500px;
+            height: 400px;
+            border: 2px solid gray;
+            margin: 50px 10px;
+        }
+    </style>
+    <script src="../../../resources/js-test-pre.js"></script>
+    <script src="../../../resources/ui-helper.js"></script>
+    <script>
+        jsTestIsAsync = true;
+
+        var iframeTarget;
+        var overflowInIframeElement;
+        var overflowInIframeWheelEventCount = 0;
+        var iframeWindowWheelEventCount = 0;
+        var mainFrameWheelEventCount = 0;
+
+        async function scrollTest()
+        {
+            iframeTarget = document.getElementById('target');
+            overflowInIframeElement = iframeTarget.contentDocument.getElementById('scroller');
+            
+            overflowInIframeElement.addEventListener('wheel', () => {
+                ++overflowInIframeWheelEventCount;
+            }, false);
+
+            iframeTarget.contentWindow.addEventListener('wheel', () => {
+                ++iframeWindowWheelEventCount;
+            }, false);
+
+            window.addEventListener('wheel', () => {
+                ++mainFrameWheelEventCount;
+            }, false);
+
+            debug("Start with main frame scrolled, iframe scrolled, overflow scrolled to top");
+            window.scrollTo(0, 20);
+            iframeTarget.contentWindow.scrollTo(0, 100);
+
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '100');
+            shouldBe('overflowInIframeElement.scrollTop', '0');
+            shouldBe('window.pageYOffset', '20');
+
+            shouldBe('mainFrameWheelEventCount', '0');
+            shouldBe('iframeWindowWheelEventCount', '0');
+            shouldBe('overflowInIframeWheelEventCount', '0');
+
+            if (!window.eventSender) {
+                finishJSTest();
+                return;
+            }
+
+            eventSender.mouseMoveTo(200, 350); // Over the overflow inside the iframe.
+            eventSender.monitorWheelEvents();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, 'began', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, 'ended', 'none');
+            await UIHelper.waitForScrollCompletion();
+
+            debug('After scroll');
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '0');
+            shouldBe('overflowInIframeElement.scrollTop', '0');
+            shouldBe('window.pageYOffset', '20');
+
+            shouldBe('mainFrameWheelEventCount', '0');
+            shouldBe('iframeWindowWheelEventCount', '2');
+            shouldBe('overflowInIframeWheelEventCount', '2');
+
+            finishJSTest();
+        }
+
+        window.addEventListener('load', () => {
+            description("Tests that wheel events still go to the innermost scroller even if latching skips it. Also tests that wheel events don't propagate between frames.");
+            setTimeout(scrollTest, 0);
+            
+        }, false);
+    </script>
+
+</head>
+<body>
+    <iframe id="target"
+     srcdoc= "
+        <style>
+            body {
+                height: 1200px;
+            }
+            #scroller {
+                width: 400px;
+                height: 400px;
+                border: 1px solid black;
+                margin: 10px;
+                margin-top: 200px;
+                overflow: scroll;
+            }
+            .contents {
+                height: 300%;
+                background-image: repeating-linear-gradient(silver, white 200px);
+            }
+        </style>
+
+        <p>iframe contents</p>
+        <div id='scroller'>
+            <div class='contents'>
+                <p>Overflow contents</p>
+            </div>
+        </div>">
+    </iframe>
+</div>
+
+<div id="console"></div>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt b/LayoutTests/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt
new file mode 100644
index 0000000..4185406
--- /dev/null
+++ b/LayoutTests/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt
@@ -0,0 +1,17 @@
+
+Scrolls over a scrolled-to-top overflow inside a scrolled iframe, testing that the iframe scrolls.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS iframeTarget.contentWindow.pageYOffset is 100
+PASS overflowInIframeElement.scrollTop is 0
+PASS window.pageYOffset is 20
+After scroll
+FAIL iframeTarget.contentWindow.pageYOffset should be 0. Was 100.
+FAIL overflowInIframeElement.scrollTop should be 0. Was 200.
+PASS window.pageYOffset is 20
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/scrolling/latching/overflow-in-iframe-latching.html b/LayoutTests/fast/scrolling/latching/overflow-in-iframe-latching.html
new file mode 100644
index 0000000..4c2d1ac
--- /dev/null
+++ b/LayoutTests/fast/scrolling/latching/overflow-in-iframe-latching.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <style>
+        body {
+            height: 2000px;
+        }
+
+        iframe {
+            width: 500px;
+            height: 400px;
+            border: 2px solid gray;
+            margin: 50px 10px;
+        }
+    </style>
+    <script src="../../../resources/js-test-pre.js"></script>
+    <script src="../../../resources/ui-helper.js"></script>
+    <script>
+        jsTestIsAsync = true;
+
+        var iframeTarget;
+        var overflowInIframeElement;
+
+        async function scrollTest()
+        {
+            iframeTarget = document.getElementById('target');
+            overflowInIframeElement = iframeTarget.contentDocument.getElementById('scroller');
+
+            window.scrollTo(0, 20);
+            iframeTarget.contentWindow.scrollTo(0, 100);
+
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '100');
+            shouldBe('overflowInIframeElement.scrollTop', '0');
+            shouldBe('window.pageYOffset', '20');
+
+            if (!window.eventSender) {
+                finishJSTest();
+                return;
+            }
+
+            eventSender.mouseMoveTo(200, 350); // Over the overflow.
+            eventSender.monitorWheelEvents();
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, 'began', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -10, 'changed', 'none');
+            eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, 'ended', 'none');
+            await UIHelper.waitForScrollCompletion();
+
+            debug('After scroll');
+            shouldBe('iframeTarget.contentWindow.pageYOffset', '0');
+            shouldBe('overflowInIframeElement.scrollTop', '0');
+            shouldBe('window.pageYOffset', '20');
+
+            finishJSTest();
+        }
+
+        window.addEventListener('load', () => {
+            description("Scrolls over a scrolled-to-top overflow inside a scrolled iframe, testing that the iframe scrolls.");
+            setTimeout(scrollTest, 0);
+            
+        }, false);
+    </script>
+
+</head>
+<body>
+    <iframe id="target"
+     srcdoc= "
+        <style>
+            body {
+                height: 1200px;
+            }
+            #scroller {
+                width: 400px;
+                height: 400px;
+                border: 1px solid black;
+                margin: 10px;
+                margin-top: 200px;
+                overflow: scroll;
+            }
+            .contents {
+                height: 300%;
+                background-image: repeating-linear-gradient(silver, white 200px);
+            }
+        </style>
+
+        <p>iframe contents</p>
+        <div id='scroller'>
+            <div class='contents'>
+                <p>Overflow contents</p>
+            </div>
+        </div>">
+    </iframe>
+</div>
+
+<div id="console"></div>
+<script src="../../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/platform/mac-wk1/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt b/LayoutTests/platform/mac-wk1/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt
new file mode 100644
index 0000000..9e8a2865
--- /dev/null
+++ b/LayoutTests/platform/mac-wk1/fast/scrolling/latching/iframe-latch-small-deltas-expected.txt
@@ -0,0 +1,20 @@
+
+Tests that a second scroll with small x/y deltas uses latching from an earlier scroll.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS iframeTarget.contentWindow.pageYOffset is 0
+PASS window.pageYOffset is 200
+After scroll
+FAIL iframeTarget.contentWindow.pageYOffset should be 500. Was 305.
+PASS window.pageYOffset is 200
+After wait
+FAIL iframeTarget.contentWindow.pageYOffset should be 500. Was 305.
+PASS window.pageYOffset is 200
+FAIL iframeTarget.contentWindow.pageYOffset should be 480. Was 300.
+PASS window.pageYOffset is 200
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/platform/mac-wk1/fast/scrolling/latching/latching-and-wheel-events-expected.txt b/LayoutTests/platform/mac-wk1/fast/scrolling/latching/latching-and-wheel-events-expected.txt
new file mode 100644
index 0000000..f9e6fce
--- /dev/null
+++ b/LayoutTests/platform/mac-wk1/fast/scrolling/latching/latching-and-wheel-events-expected.txt
@@ -0,0 +1,24 @@
+
+Tests that wheel events still go to the innermost scroller even if latching skips it. Also tests that wheel events don't propagate between frames.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Start with main frame scrolled, iframe scrolled, overflow scrolled to top
+PASS iframeTarget.contentWindow.pageYOffset is 100
+PASS overflowInIframeElement.scrollTop is 0
+PASS window.pageYOffset is 20
+PASS mainFrameWheelEventCount is 0
+PASS iframeWindowWheelEventCount is 0
+PASS overflowInIframeWheelEventCount is 0
+After scroll
+FAIL iframeTarget.contentWindow.pageYOffset should be 0. Was 100.
+FAIL overflowInIframeElement.scrollTop should be 0. Was 210.
+PASS window.pageYOffset is 20
+PASS mainFrameWheelEventCount is 0
+FAIL iframeWindowWheelEventCount should be 2. Was 3.
+FAIL overflowInIframeWheelEventCount should be 2. Was 3.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/platform/mac-wk1/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt b/LayoutTests/platform/mac-wk1/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt
new file mode 100644
index 0000000..87eb203
--- /dev/null
+++ b/LayoutTests/platform/mac-wk1/fast/scrolling/latching/overflow-in-iframe-latching-expected.txt
@@ -0,0 +1,17 @@
+
+Scrolls over a scrolled-to-top overflow inside a scrolled iframe, testing that the iframe scrolls.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS iframeTarget.contentWindow.pageYOffset is 100
+PASS overflowInIframeElement.scrollTop is 0
+PASS window.pageYOffset is 20
+After scroll
+FAIL iframeTarget.contentWindow.pageYOffset should be 0. Was 100.
+FAIL overflowInIframeElement.scrollTop should be 0. Was 210.
+PASS window.pageYOffset is 20
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/resources/ui-helper.js b/LayoutTests/resources/ui-helper.js
index d1124ff..08a4a1b 100644
--- a/LayoutTests/resources/ui-helper.js
+++ b/LayoutTests/resources/ui-helper.js
@@ -43,6 +43,15 @@
         });
     }
 
+    static async waitForScrollCompletion()
+    {
+        return new Promise(resolve => {
+            eventSender.callAfterScrollingCompletes(() => {
+                requestAnimationFrame(resolve);
+            });
+        });
+    }
+
     static async animationFrame()
     {
         return new Promise(requestAnimationFrame);