<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[198805] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/198805">198805</a></dd>
<dt>Author</dt> <dd>tonikitoo@webkit.org</dd>
<dt>Date</dt> <dd>2016-03-29 15:17:04 -0700 (Tue, 29 Mar 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Wheel events' latching state is not reset when appropriate
https://bugs.webkit.org/show_bug.cgi?id=155746

Reviewed by Simon Fraser.

Source/WebCore:

When one performs a two fingers scroll (with trackpad),
it might either trigger a kinetic scroll movement (by
flickering with both fingers in a given direction) or perform
a static scroll movement (without the flicker animation).

In case of the first scenario (kinetic), the container being
scrolled is &quot;latched&quot; during the scroll animation and properly unlatched
once the scroll ends. See the call to 'shouldResetLatching' in EventHandlerMac.mm.
When transitioning from non-momentum to momentum scroll, the following events are seen:
- 'phase PlatformWheelEventPhaseEnded' 'momentumPhase PlatformWheelEventPhaseNone' -&gt; end of static scroll
- 'phase PlatformWheelEventPhaseNone' 'momentumPhase PlatformWheelEventPhaseBegan' -&gt; start of momentum scroll

On the second scenario (static scroll), when the scroll movement ends,
the latched state is not reset. This might actually cause scrolling elsewhere on the page to scroll
the previously latched container.
Note that, only specific wheel event below is fired when static scroll ends:
- 'phase PlatformWheelEventPhaseEnded' 'momentumPhase PlatformWheelEventPhaseNone'

In order to fix this, patch installs a timer as soon as a non-momentum
wheel scroll event end fires. It works as follows:

- If the timer fires, the latched state is reset, preventing scrolling getting stuck to
a previously latched scrollable.
- If platform code starts sending momentum wheel scroll events before it times out, the timer is
stopped and the latched state remains untouched (this is a flick scroll case).
- If a new wheel scroll gesture starts, the latched state is manually reset
and the timer stopped.

Note that given that latched state logic applies primarily to main frames,
the timer only gets manipulated for main frame objects.

Test: tiled-drawing/scrolling/scroll-iframe-latched-selects.html

* page/EventHandler.cpp:
(WebCore::EventHandler::doOrScheduleClearLatchedStateIfNeeded):
* page/EventHandler.h:
* page/MainFrame.cpp:
(WebCore::MainFrame::MainFrame):
(WebCore::MainFrame::~MainFrame):
* page/MainFrame.h:
* page/mac/EventHandlerMac.mm:
(WebCore::scrollableAreaForContainerNode):
(WebCore::latchedToFrameOrBody):
(WebCore::areWheelEventsTransitioningToMomentumScrolling):
(WebCore::areWheelEventsEndingNonMomentumScrolling):
(WebCore::EventHandler::doOrScheduleClearLatchedStateIfNeeded):

LayoutTests:

* tiled-drawing/scrolling/resources/selects-iframe.html: Added.
* tiled-drawing/scrolling/scroll-iframe-latched-selects.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCorepageEventHandlercpp">trunk/Source/WebCore/page/EventHandler.cpp</a></li>
<li><a href="#trunkSourceWebCorepageEventHandlerh">trunk/Source/WebCore/page/EventHandler.h</a></li>
<li><a href="#trunkSourceWebCorepageMainFrameh">trunk/Source/WebCore/page/MainFrame.h</a></li>
<li><a href="#trunkSourceWebCorepagemacEventHandlerMacmm">trunk/Source/WebCore/page/mac/EventHandlerMac.mm</a></li>
<li><a href="#trunkSourceWebCoreplatformPlatformWheelEventh">trunk/Source/WebCore/platform/PlatformWheelEvent.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTeststileddrawingscrollingresourcesselectsiframehtml">trunk/LayoutTests/tiled-drawing/scrolling/resources/selects-iframe.html</a></li>
<li><a href="#trunkLayoutTeststileddrawingscrollingscrolliframelatchedselectsexpectedtxt">trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects-expected.txt</a></li>
<li><a href="#trunkLayoutTeststileddrawingscrollingscrolliframelatchedselectshtml">trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/LayoutTests/ChangeLog        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -1,3 +1,13 @@
</span><ins>+2016-03-29  Antonio Gomes  &lt;tonikitoo@webkit.org&gt;
+
+        Wheel events' latching state is not reset when appropriate
+        https://bugs.webkit.org/show_bug.cgi?id=155746
+
+        Reviewed by Simon Fraser.
+
+        * tiled-drawing/scrolling/resources/selects-iframe.html: Added.
+        * tiled-drawing/scrolling/scroll-iframe-latched-selects.html: Added.
+
</ins><span class="cx"> 2016-03-29  Saam barati  &lt;sbarati@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         &quot;Can not&quot; =&gt; &quot;cannot&quot; in String.prototype error messages
</span></span></pre></div>
<a id="trunkLayoutTeststileddrawingscrollingresourcesselectsiframehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/tiled-drawing/scrolling/resources/selects-iframe.html (0 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/tiled-drawing/scrolling/resources/selects-iframe.html                                (rev 0)
+++ trunk/LayoutTests/tiled-drawing/scrolling/resources/selects-iframe.html        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -0,0 +1,23 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+  &lt;body&gt;
+    &lt;h1&gt;This is a Heading&lt;/h1&gt;
+    &lt;p&gt;This is a paragraph.&lt;/p&gt;
+    &lt;select id=&quot;selectLeft&quot; size=&quot;5&quot; multiple=&quot;multiple&quot;&gt;
+      &lt;option value=&quot;&quot; &gt;(No Driver Filter)&lt;/option&gt;
+      &lt;option value=&quot;drivername=''&quot;&gt;&lt;/option&gt;
+      &lt;option value=&quot;drivername='alex'&quot;&gt;alex&lt;/option&gt;
+      &lt;option value=&quot;drivername='marc'&quot;&gt;marc&lt;/option&gt;
+      &lt;option value=&quot;drivername='frank'&quot;&gt;frank&lt;/option&gt;
+      &lt;option value=&quot;drivername='james'&quot;&gt;james&lt;/option&gt;
+    &lt;/select&gt;
+    &lt;select id=&quot;selectRight&quot; size=&quot;5&quot; multiple=&quot;multiple&quot;&gt;
+      &lt;option value=&quot;&quot; &gt;(No Driver Filter)&lt;/option&gt;
+      &lt;option value=&quot;drivername=''&quot;&gt;&lt;/option&gt;
+      &lt;option value=&quot;drivername='alex'&quot;&gt;alex&lt;/option&gt;
+      &lt;option value=&quot;drivername='marc'&quot;&gt;marc&lt;/option&gt;
+      &lt;option value=&quot;drivername='frank'&quot;&gt;frank&lt;/option&gt;
+      &lt;option value=&quot;drivername='james'&quot;&gt;james&lt;/option&gt;
+    &lt;/select&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTeststileddrawingscrollingscrolliframelatchedselectsexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects-expected.txt (0 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects-expected.txt                                (rev 0)
+++ trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects-expected.txt        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -0,0 +1,15 @@
</span><ins>+
+Tests that latched state logic does not get stuck scrolling a specific &lt;select&gt;. To manually run this test, place the mouse pointer
+within one left &lt;select&gt; boundary, and then use the mouse wheel or a two-finger to scroll down the list, without momentum scrolling.
+Do the same for the right &lt;select&gt;. The left &lt;select&gt; should not scroll when trying to scroll the right &lt;select&gt;.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+PASS Leftmost &lt;select&gt; was scrolled
+PASS Leftmost &lt;select&gt; was not scrolled by the latch state logic
+PASS Rightmost &lt;select&gt; was properly scrolled.
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTeststileddrawingscrollingscrolliframelatchedselectshtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects.html (0 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects.html                                (rev 0)
+++ trunk/LayoutTests/tiled-drawing/scrolling/scroll-iframe-latched-selects.html        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -0,0 +1,80 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;link rel=&quot;help&quot; href=&quot;http://www.w3.org/TR/DOM-Level-3-Events/#events-WheelEvent&quot;&gt;
+&lt;script src=&quot;../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;setupTopLevel();&quot;&gt;
+&lt;script&gt;
+window.jsTestIsAsync = true;
+
+var iframeTarget;
+var selectLeft;
+var selectRight;
+var previousSelectLeftScrollPosition;
+
+function checkForScroll()
+{
+    if (selectLeft.scrollTop == previousSelectLeftScrollPosition)
+        testPassed(&quot;Leftmost &lt;select&gt; was not scrolled by the latch state logic&quot;);
+    else
+        testFailed(&quot;Leftmost &lt;select&gt; was incorrectly scrolled by wrong latch logic.&quot; + &quot; &quot; + selectLeft.scrollTop + &quot; &quot; + previousSelectLeftScrollPosition);
+
+    if (selectRight.scrollTop &gt; 0)
+        testPassed(&quot;Rightmost &lt;select&gt; was properly scrolled.&quot;);
+    else
+        testFailed(&quot;Rightmost &lt;select&gt; was not scrolled though it should.&quot;);
+
+    finishJSTest();
+}
+function scrollRightMostSelect()
+{
+    if (selectLeft.scrollTop &gt; 0)
+        testPassed(&quot;Leftmost &lt;select&gt; was scrolled&quot;);
+    else
+        testFailed(&quot;Leftmost &lt;select&gt; was not scrolled&quot;);
+    previousSelectLeftScrollPosition = selectLeft.scrollTop;
+
+    var startPosX = Math.round(selectRight.offsetLeft) + 10;
+    var startPosY = Math.round(selectRight.offsetTop) + 10;
+    eventSender.mouseMoveTo(startPosX, startPosY);
+    eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, &quot;began&quot;, &quot;none&quot;);
+    eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, &quot;changed&quot;, &quot;none&quot;);
+    eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, &quot;ended&quot;, &quot;none&quot;);
+    eventSender.callAfterScrollingCompletes(checkForScroll);
+}
+
+function scrollLeftMostSelect()
+{
+    var startPosX = Math.round(selectLeft.offsetLeft) + 10;
+    var startPosY = Math.round(selectLeft.offsetTop) + 10;
+    eventSender.mouseMoveTo(startPosX, startPosY);
+    eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, &quot;began&quot;, &quot;none&quot;);
+    eventSender.mouseScrollByWithWheelAndMomentumPhases(0, -1, &quot;changed&quot;, &quot;none&quot;);
+    eventSender.mouseScrollByWithWheelAndMomentumPhases(0, 0, &quot;ended&quot;, &quot;none&quot;);
+    eventSender.callAfterScrollingCompletes(scrollRightMostSelect);
+}
+
+function setupTopLevel()
+{
+    if (window.eventSender) {
+        eventSender.monitorWheelEvents();
+
+        iframeTarget = document.getElementById(&quot;target&quot;);
+        selectLeft = window.frames[&quot;target&quot;].document.getElementById(&quot;selectLeft&quot;);
+        selectRight = window.frames[&quot;target&quot;].document.getElementById(&quot;selectRight&quot;);
+
+        setTimeout(scrollLeftMostSelect, 0);
+    }
+}
+&lt;/script&gt;
+&lt;iframe id=&quot;target&quot; name=&quot;target&quot; height=&quot;400&quot; width=&quot;400&quot; src=&quot;resources/selects-iframe.html&quot;&gt;&lt;/iframe&gt;
+&lt;div id=&quot;console&quot;&gt;&lt;/div&gt;
+&lt;script&gt;
+description(&quot;Tests that latched state logic does not get stuck scrolling a specific &amp;lt;select&amp;gt;. To manually run this test, place the mouse pointer&lt;br&gt;&quot; +
+            &quot;within one left &amp;lt;select&amp;gt; boundary, and then use the mouse wheel or a two-finger to scroll down the list, without momentum scrolling.&lt;br&gt;&quot; +
+            &quot;Do the same for the right &amp;lt;select&amp;gt;. The left &amp;lt;select&amp;gt; should not scroll when trying to scroll the right &amp;lt;select&amp;gt;.&quot;);
+&lt;/script&gt;
+&lt;script src=&quot;../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/Source/WebCore/ChangeLog        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -1,3 +1,57 @@
</span><ins>+2016-03-29  Antonio Gomes  &lt;tonikitoo@webkit.org&gt;
+
+        Wheel events' latching state is not reset when appropriate
+        https://bugs.webkit.org/show_bug.cgi?id=155746
+
+        Reviewed by Simon Fraser.
+
+        When one performs a two fingers scroll (with trackpad),
+        it might either trigger a kinetic scroll movement (by
+        flickering with both fingers in a given direction) or perform
+        a static scroll movement (without the flicker animation).
+
+        In case of the first scenario (kinetic), the container being
+        scrolled is &quot;latched&quot; during the scroll animation and properly unlatched
+        once the scroll ends. See the call to 'shouldResetLatching' in EventHandlerMac.mm.
+        When transitioning from non-momentum to momentum scroll, the following events are seen:
+        - 'phase PlatformWheelEventPhaseEnded' 'momentumPhase PlatformWheelEventPhaseNone' -&gt; end of static scroll
+        - 'phase PlatformWheelEventPhaseNone' 'momentumPhase PlatformWheelEventPhaseBegan' -&gt; start of momentum scroll
+
+        On the second scenario (static scroll), when the scroll movement ends,
+        the latched state is not reset. This might actually cause scrolling elsewhere on the page to scroll
+        the previously latched container.
+        Note that, only specific wheel event below is fired when static scroll ends:
+        - 'phase PlatformWheelEventPhaseEnded' 'momentumPhase PlatformWheelEventPhaseNone'
+
+        In order to fix this, patch installs a timer as soon as a non-momentum
+        wheel scroll event end fires. It works as follows:
+
+        - If the timer fires, the latched state is reset, preventing scrolling getting stuck to
+        a previously latched scrollable.
+        - If platform code starts sending momentum wheel scroll events before it times out, the timer is
+        stopped and the latched state remains untouched (this is a flick scroll case).
+        - If a new wheel scroll gesture starts, the latched state is manually reset
+        and the timer stopped.
+
+        Note that given that latched state logic applies primarily to main frames,
+        the timer only gets manipulated for main frame objects.
+
+        Test: tiled-drawing/scrolling/scroll-iframe-latched-selects.html
+
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::doOrScheduleClearLatchedStateIfNeeded):
+        * page/EventHandler.h:
+        * page/MainFrame.cpp:
+        (WebCore::MainFrame::MainFrame):
+        (WebCore::MainFrame::~MainFrame):
+        * page/MainFrame.h:
+        * page/mac/EventHandlerMac.mm:
+        (WebCore::scrollableAreaForContainerNode):
+        (WebCore::latchedToFrameOrBody):
+        (WebCore::areWheelEventsTransitioningToMomentumScrolling):
+        (WebCore::areWheelEventsEndingNonMomentumScrolling):
+        (WebCore::EventHandler::doOrScheduleClearLatchedStateIfNeeded):
+
</ins><span class="cx"> 2016-03-29  Alex Christensen  &lt;achristensen@webkit.org&gt;
</span><span class="cx"> 
</span><span class="cx">         Fix Windows build.
</span></span></pre></div>
<a id="trunkSourceWebCorepageEventHandlercpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/EventHandler.cpp (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/page/EventHandler.cpp        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/Source/WebCore/page/EventHandler.cpp        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -379,6 +379,9 @@
</span><span class="cx"> #if ENABLE(CURSOR_SUPPORT)
</span><span class="cx">     , m_cursorUpdateTimer(*this, &amp;EventHandler::cursorUpdateTimerFired)
</span><span class="cx"> #endif
</span><ins>+#if PLATFORM(MAC)
+    , m_pendingMomentumWheelEventsTimer(*this, &amp;EventHandler::clearLatchedState)
+#endif
</ins><span class="cx">     , m_autoscrollController(std::make_unique&lt;AutoscrollController&gt;())
</span><span class="cx"> #if !ENABLE(IOS_TOUCH_EVENTS)
</span><span class="cx">     , m_fakeMouseMoveEventTimer(*this, &amp;EventHandler::fakeMouseMoveEventTimerFired)
</span><span class="lines">@@ -2591,6 +2594,10 @@
</span><span class="cx">     return m_lastKnownMousePosition;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void EventHandler::clearOrScheduleClearingLatchedStateIfNeeded(const PlatformWheelEvent&amp;)
+{
+    clearLatchedState();
+}
</ins><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx"> bool EventHandler::handleWheelEvent(const PlatformWheelEvent&amp; event)
</span></span></pre></div>
<a id="trunkSourceWebCorepageEventHandlerh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/EventHandler.h (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/page/EventHandler.h        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/Source/WebCore/page/EventHandler.h        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -461,6 +461,7 @@
</span><span class="cx">     void autoHideCursorTimerFired();
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+    void clearOrScheduleClearingLatchedStateIfNeeded(const PlatformWheelEvent&amp;);
</ins><span class="cx">     void clearLatchedState();
</span><span class="cx"> 
</span><span class="cx">     Frame&amp; m_frame;
</span><span class="lines">@@ -489,6 +490,9 @@
</span><span class="cx">     Timer m_cursorUpdateTimer;
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+#if PLATFORM(MAC)
+    Timer m_pendingMomentumWheelEventsTimer;
+#endif
</ins><span class="cx">     std::unique_ptr&lt;AutoscrollController&gt; m_autoscrollController;
</span><span class="cx">     bool m_mouseDownMayStartAutoscroll { false };
</span><span class="cx">     bool m_mouseDownWasInSubframe { false };
</span></span></pre></div>
<a id="trunkSourceWebCorepageMainFrameh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/MainFrame.h (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/page/MainFrame.h        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/Source/WebCore/page/MainFrame.h        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -26,6 +26,7 @@
</span><span class="cx"> #ifndef MainFrame_h
</span><span class="cx"> #define MainFrame_h
</span><span class="cx"> 
</span><ins>+#include &quot;EventHandler.h&quot;
</ins><span class="cx"> #include &quot;Frame.h&quot;
</span><span class="cx"> #include &lt;wtf/Vector.h&gt;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebCorepagemacEventHandlerMacmm"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/mac/EventHandlerMac.mm (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/page/mac/EventHandlerMac.mm        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/Source/WebCore/page/mac/EventHandlerMac.mm        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -75,6 +75,8 @@
</span><span class="cx"> const double EventHandler::TextDragDelay = 0.15;
</span><span class="cx"> #endif
</span><span class="cx"> 
</span><ins>+const double resetLatchedStateTimeout = 0.1;
+
</ins><span class="cx"> static RetainPtr&lt;NSEvent&gt;&amp; currentNSEventSlot()
</span><span class="cx"> {
</span><span class="cx">     static NeverDestroyed&lt;RetainPtr&lt;NSEvent&gt;&gt; event;
</span><span class="lines">@@ -931,8 +933,34 @@
</span><span class="cx">     return is&lt;HTMLFrameSetElement&gt;(container) || is&lt;HTMLBodyElement&gt;(container);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void EventHandler::clearOrScheduleClearingLatchedStateIfNeeded(const PlatformWheelEvent&amp; event)
+{
+    if (!m_frame.isMainFrame())
+        return;
+
+    // Platform does not provide an indication that it will switch from non-momentum to momentum scrolling
+    // when handling wheel events.
+    // Logic below installs a timer when non-momentum scrolling ends. If momentum scroll does not start within that interval,
+    // reset the latched state. If it does, stop the timer, leaving the latched state untouched.
+    if (!m_pendingMomentumWheelEventsTimer.isActive()) {
+        if (event.isEndOfNonMomentumScroll())
+            m_pendingMomentumWheelEventsTimer.startOneShot(resetLatchedStateTimeout);
+    } else {
+        // If another wheel event scrolling starts, stop the timer manually, and reset the latched state immediately.
+        if (event.shouldConsiderLatching()) {
+            m_frame.mainFrame().resetLatchingState();
+            m_pendingMomentumWheelEventsTimer.stop();
+        } else if (event.isTransitioningToMomentumScroll()) {
+            // Wheel events machinary is transitioning to momenthum scrolling, so no need to reset latched state. Stop the timer.
+            m_pendingMomentumWheelEventsTimer.stop();
+        }
+    }
+}
+
</ins><span class="cx"> void EventHandler::platformPrepareForWheelEvents(const PlatformWheelEvent&amp; wheelEvent, const HitTestResult&amp; result, RefPtr&lt;Element&gt;&amp; wheelEventTarget, RefPtr&lt;ContainerNode&gt;&amp; scrollableContainer, ScrollableArea*&amp; scrollableArea, bool&amp; isOverWidget)
</span><span class="cx"> {
</span><ins>+    clearOrScheduleClearingLatchedStateIfNeeded(wheelEvent);
+
</ins><span class="cx">     FrameView* view = m_frame.view();
</span><span class="cx"> 
</span><span class="cx">     scrollableContainer = nullptr;
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformPlatformWheelEventh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/platform/PlatformWheelEvent.h (198804 => 198805)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/PlatformWheelEvent.h        2016-03-29 22:15:27 UTC (rev 198804)
+++ trunk/Source/WebCore/platform/PlatformWheelEvent.h        2016-03-29 22:17:04 UTC (rev 198805)
</span><span class="lines">@@ -160,6 +160,8 @@
</span><span class="cx">         bool shouldConsiderLatching() const;
</span><span class="cx">         bool shouldResetLatching() const;
</span><span class="cx">         bool isEndOfMomentumScroll() const;
</span><ins>+        bool isEndOfNonMomentumScroll() const;
+        bool isTransitioningToMomentumScroll() const;
</ins><span class="cx"> #else
</span><span class="cx">         bool useLatchedEventElement() const { return false; }
</span><span class="cx"> #endif
</span><span class="lines">@@ -211,6 +213,15 @@
</span><span class="cx">         return m_phase == PlatformWheelEventPhaseNone &amp;&amp; m_momentumPhase == PlatformWheelEventPhaseEnded;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    inline bool PlatformWheelEvent::isEndOfNonMomentumScroll() const
+    {
+        return m_phase == PlatformWheelEventPhaseEnded &amp;&amp; m_momentumPhase == PlatformWheelEventPhaseNone;
+    }
+
+    inline bool PlatformWheelEvent::isTransitioningToMomentumScroll() const
+    {
+        return m_phase == PlatformWheelEventPhaseNone &amp;&amp; m_momentumPhase == PlatformWheelEventPhaseBegan;
+    }
</ins><span class="cx"> #endif
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebCore
</span></span></pre>
</div>
</div>

</body>
</html>