<!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>[172616] trunk/Source/WebCore</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/172616">172616</a></dd>
<dt>Author</dt> <dd>commit-queue@webkit.org</dd>
<dt>Date</dt> <dd>2014-08-14 17:21:25 -0700 (Thu, 14 Aug 2014)</dd>
</dl>

<h3>Log Message</h3>
<pre>Implement scroll snapping animations on Mac
https://bugs.webkit.org/show_bug.cgi?id=135768

Patch by Wenson Hsieh &lt;wenson_hsieh@apple.com&gt; on 2014-08-14
Reviewed by Beth Dakin.

Implementing the scroll snap animations required for snapping in both overflow and mainframe areas on Mac.
Since we receive a series of discrete wheel events, we need to predict the distance we need to traverse,
compute the appropriate snap point, and then compute an animation curve to that location. The snap animations
are split into two types: snapping, which handles letting go of the trackpad with zero velocity, and gliding,
which handles flick gestures. In both cases, sinusoidal curves are used to ease animation to the target
location. In the former case, the initial velocity is low, and increases to a maximum value during the middle
of the animation before decreasing to 0. In the latter case, the curve is computed such that the initial
velocity matches the user's scroll velocity, and the final velocity matches a lower final velocity that is
somewhat arbitrarily computed. (See the FIXME in AxisScrollSnapOffsets::initializeGlideParameters). How the
equations and constants were chosen is described in greater detail in the comments above
AxisScrollSnapOffsets::computeGlideDelta and AxisScrollSnapOffsets::computeSnapDelta. Note that the final
velocity should ideally be equal to 0. However, with this particular curve, this caused the animation to feel
too slow near the snap point.

No tests, since there is no observable change in behavior. Tests will be included when iOS and Mac scrolling
areas hook into this code.

* PlatformMac.cmake:
* WebCore.xcodeproj/project.pbxproj:
* page/scrolling/AxisScrollSnapOffsets.cpp:
(WebCore::closestSnapOffset): Computes the closest snap offset given velocity and prdicted destination.
* page/scrolling/AxisScrollSnapOffsets.h:
* platform/mac/AxisScrollSnapAnimator.h: Added.
(WebCore::AxisScrollSnapAnimatorClient::~AxisScrollSnapAnimatorClient):
* platform/mac/AxisScrollSnapAnimator.mm: Added.
(WebCore::toWheelEventStatus): Converts a pair of PlatformWheelEventPhases to a WheelEventStatus.
(WebCore::projectedInertialScrollDistance): Attempts to predict the distance covered by the inertial scrolling phase.
(WebCore::AxisScrollSnapAnimator::AxisScrollSnapAnimator):
(WebCore::AxisScrollSnapAnimator::handleWheelEvent): Updates the internal state of the AxisScrollSnapAnimator, given a PlatformWheelEvent
(WebCore::AxisScrollSnapAnimator::shouldOverrideWheelEvent): Determines whether or not we should override a wheel event given the animator's internal state.
(WebCore::AxisScrollSnapAnimator::scrollSnapAnimationUpdate): Updates a single loop of the scroll snapping animation.
(WebCore::AxisScrollSnapAnimator::beginScrollSnapAnimation):
(WebCore::AxisScrollSnapAnimator::endScrollSnapAnimation):
(WebCore::AxisScrollSnapAnimator::computeSnapDelta): See comments for more information.
(WebCore::AxisScrollSnapAnimator::computeGlideDelta): See comments for more information.
(WebCore::AxisScrollSnapAnimator::initializeGlideParameters):
(WebCore::AxisScrollSnapAnimator::pushInitialWheelDelta):
(WebCore::AxisScrollSnapAnimator::averageInitialWheelDelta):
(WebCore::AxisScrollSnapAnimator::clearInitialWheelDeltaWindow):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
<li><a href="#trunkSourceWebCorePlatformMaccmake">trunk/Source/WebCore/PlatformMac.cmake</a></li>
<li><a href="#trunkSourceWebCoreWebCorexcodeprojprojectpbxproj">trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj</a></li>
<li><a href="#trunkSourceWebCorepagescrollingAxisScrollSnapOffsetsh">trunk/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkSourceWebCoreplatformmacAxisScrollSnapAnimatorh">trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.h</a></li>
<li><a href="#trunkSourceWebCoreplatformmacAxisScrollSnapAnimatormm">trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.mm</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (172615 => 172616)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/ChangeLog        2014-08-15 00:18:25 UTC (rev 172615)
+++ trunk/Source/WebCore/ChangeLog        2014-08-15 00:21:25 UTC (rev 172616)
</span><span class="lines">@@ -1,3 +1,50 @@
</span><ins>+2014-08-14  Wenson Hsieh  &lt;wenson_hsieh@apple.com&gt;
+
+        Implement scroll snapping animations on Mac
+        https://bugs.webkit.org/show_bug.cgi?id=135768
+
+        Reviewed by Beth Dakin.
+
+        Implementing the scroll snap animations required for snapping in both overflow and mainframe areas on Mac.
+        Since we receive a series of discrete wheel events, we need to predict the distance we need to traverse,
+        compute the appropriate snap point, and then compute an animation curve to that location. The snap animations
+        are split into two types: snapping, which handles letting go of the trackpad with zero velocity, and gliding,
+        which handles flick gestures. In both cases, sinusoidal curves are used to ease animation to the target
+        location. In the former case, the initial velocity is low, and increases to a maximum value during the middle
+        of the animation before decreasing to 0. In the latter case, the curve is computed such that the initial
+        velocity matches the user's scroll velocity, and the final velocity matches a lower final velocity that is
+        somewhat arbitrarily computed. (See the FIXME in AxisScrollSnapOffsets::initializeGlideParameters). How the
+        equations and constants were chosen is described in greater detail in the comments above
+        AxisScrollSnapOffsets::computeGlideDelta and AxisScrollSnapOffsets::computeSnapDelta. Note that the final
+        velocity should ideally be equal to 0. However, with this particular curve, this caused the animation to feel
+        too slow near the snap point.
+
+        No tests, since there is no observable change in behavior. Tests will be included when iOS and Mac scrolling
+        areas hook into this code.
+
+        * PlatformMac.cmake:
+        * WebCore.xcodeproj/project.pbxproj: 
+        * page/scrolling/AxisScrollSnapOffsets.cpp: 
+        (WebCore::closestSnapOffset): Computes the closest snap offset given velocity and prdicted destination.
+        * page/scrolling/AxisScrollSnapOffsets.h:
+        * platform/mac/AxisScrollSnapAnimator.h: Added.
+        (WebCore::AxisScrollSnapAnimatorClient::~AxisScrollSnapAnimatorClient):
+        * platform/mac/AxisScrollSnapAnimator.mm: Added.
+        (WebCore::toWheelEventStatus): Converts a pair of PlatformWheelEventPhases to a WheelEventStatus.
+        (WebCore::projectedInertialScrollDistance): Attempts to predict the distance covered by the inertial scrolling phase.
+        (WebCore::AxisScrollSnapAnimator::AxisScrollSnapAnimator):
+        (WebCore::AxisScrollSnapAnimator::handleWheelEvent): Updates the internal state of the AxisScrollSnapAnimator, given a PlatformWheelEvent
+        (WebCore::AxisScrollSnapAnimator::shouldOverrideWheelEvent): Determines whether or not we should override a wheel event given the animator's internal state.
+        (WebCore::AxisScrollSnapAnimator::scrollSnapAnimationUpdate): Updates a single loop of the scroll snapping animation.
+        (WebCore::AxisScrollSnapAnimator::beginScrollSnapAnimation):
+        (WebCore::AxisScrollSnapAnimator::endScrollSnapAnimation):
+        (WebCore::AxisScrollSnapAnimator::computeSnapDelta): See comments for more information.
+        (WebCore::AxisScrollSnapAnimator::computeGlideDelta): See comments for more information.
+        (WebCore::AxisScrollSnapAnimator::initializeGlideParameters):
+        (WebCore::AxisScrollSnapAnimator::pushInitialWheelDelta):
+        (WebCore::AxisScrollSnapAnimator::averageInitialWheelDelta):
+        (WebCore::AxisScrollSnapAnimator::clearInitialWheelDeltaWindow):
+
</ins><span class="cx"> 2014-08-14  Saam Barati  &lt;sbarati@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Allow high fidelity type profiling to be enabled and disabled.
</span></span></pre></div>
<a id="trunkSourceWebCorePlatformMaccmake"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/PlatformMac.cmake (172615 => 172616)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/PlatformMac.cmake        2014-08-15 00:18:25 UTC (rev 172615)
+++ trunk/Source/WebCore/PlatformMac.cmake        2014-08-15 00:21:25 UTC (rev 172616)
</span><span class="lines">@@ -93,6 +93,7 @@
</span><span class="cx"> 
</span><span class="cx">     platform/graphics/opentype/OpenTypeMathData.cpp
</span><span class="cx"> 
</span><ins>+    platform/mac/AxisScrollSnapAnimator.mm
</ins><span class="cx">     platform/mac/BlockExceptions.mm
</span><span class="cx">     platform/mac/ContentFilterMac.mm
</span><span class="cx">     platform/mac/ContextMenuItemMac.mm
</span></span></pre></div>
<a id="trunkSourceWebCoreWebCorexcodeprojprojectpbxproj"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj (172615 => 172616)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj        2014-08-15 00:18:25 UTC (rev 172615)
+++ trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj        2014-08-15 00:21:25 UTC (rev 172616)
</span><span class="lines">@@ -6247,6 +6247,8 @@
</span><span class="cx">                 F42FFB461984B71600F6837F /* LengthRepeat.h in Headers */ = {isa = PBXBuildFile; fileRef = F42FFB451984B71600F6837F /* LengthRepeat.h */; };
</span><span class="cx">                 F45C231D1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F45C231B1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp */; };
</span><span class="cx">                 F45C231E1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h in Headers */ = {isa = PBXBuildFile; fileRef = F45C231C1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h */; settings = {ATTRIBUTES = (Private, ); }; };
</span><ins>+                F478755419983AFF0024A287 /* AxisScrollSnapAnimator.h in Headers */ = {isa = PBXBuildFile; fileRef = F478755219983AFF0024A287 /* AxisScrollSnapAnimator.h */; settings = {ATTRIBUTES = (Private, ); }; };
+                F478755519983AFF0024A287 /* AxisScrollSnapAnimator.mm in Sources */ = {isa = PBXBuildFile; fileRef = F478755319983AFF0024A287 /* AxisScrollSnapAnimator.mm */; };
</ins><span class="cx">                 F47A5E3E195B8C8A00483100 /* StyleScrollSnapPoints.h in Headers */ = {isa = PBXBuildFile; fileRef = F47A5E3B195B8C8A00483100 /* StyleScrollSnapPoints.h */; settings = {ATTRIBUTES = (Private, ); }; };
</span><span class="cx">                 F47A5E3F195B8E4800483100 /* StyleScrollSnapPoints.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F47A5E3A195B8C8A00483100 /* StyleScrollSnapPoints.cpp */; };
</span><span class="cx">                 F50664F7157F52DC00AC226F /* FormController.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F50664F5157F52DC00AC226F /* FormController.cpp */; };
</span><span class="lines">@@ -13766,6 +13768,8 @@
</span><span class="cx">                 F42FFB451984B71600F6837F /* LengthRepeat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LengthRepeat.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 F45C231B1995B73B00A6E2E3 /* AxisScrollSnapOffsets.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AxisScrollSnapOffsets.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 F45C231C1995B73B00A6E2E3 /* AxisScrollSnapOffsets.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AxisScrollSnapOffsets.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><ins>+                F478755219983AFF0024A287 /* AxisScrollSnapAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AxisScrollSnapAnimator.h; sourceTree = &quot;&lt;group&gt;&quot;; };
+                F478755319983AFF0024A287 /* AxisScrollSnapAnimator.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AxisScrollSnapAnimator.mm; sourceTree = &quot;&lt;group&gt;&quot;; };
</ins><span class="cx">                 F47A5E3A195B8C8A00483100 /* StyleScrollSnapPoints.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StyleScrollSnapPoints.cpp; path = style/StyleScrollSnapPoints.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 F47A5E3B195B8C8A00483100 /* StyleScrollSnapPoints.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StyleScrollSnapPoints.h; path = style/StyleScrollSnapPoints.h; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="cx">                 F50664F5157F52DC00AC226F /* FormController.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FormController.cpp; sourceTree = &quot;&lt;group&gt;&quot;; };
</span><span class="lines">@@ -16408,6 +16412,8 @@
</span><span class="cx">                 6582A14809999D6C00BEEB6D /* mac */ = {
</span><span class="cx">                         isa = PBXGroup;
</span><span class="cx">                         children = (
</span><ins>+                                F478755219983AFF0024A287 /* AxisScrollSnapAnimator.h */,
+                                F478755319983AFF0024A287 /* AxisScrollSnapAnimator.mm */,
</ins><span class="cx">                                 65A640F00533BB1F0085E777 /* BlockExceptions.h */,
</span><span class="cx">                                 65F80697054D9F86008BF776 /* BlockExceptions.mm */,
</span><span class="cx">                                 2919A1EA16B3378900787213 /* ContentFilterMac.mm */,
</span><span class="lines">@@ -26259,6 +26265,7 @@
</span><span class="cx">                                 B2227A970D00BF220071B782 /* SVGPreserveAspectRatio.h in Headers */,
</span><span class="cx">                                 088A0E0A126EF1DB00978F7A /* SVGProperty.h in Headers */,
</span><span class="cx">                                 081DD49C13BA1A6000DC7627 /* SVGPropertyInfo.h in Headers */,
</span><ins>+                                F478755419983AFF0024A287 /* AxisScrollSnapAnimator.h in Headers */,
</ins><span class="cx">                                 088A0E0B126EF1DB00978F7A /* SVGPropertyTearOff.h in Headers */,
</span><span class="cx">                                 088A0E0C126EF1DB00978F7A /* SVGPropertyTraits.h in Headers */,
</span><span class="cx">                                 B2227A9A0D00BF220071B782 /* SVGRadialGradientElement.h in Headers */,
</span><span class="lines">@@ -28174,6 +28181,7 @@
</span><span class="cx">                                 BE61039D18A9D65200DD50D7 /* JSDataCue.cpp in Sources */,
</span><span class="cx">                                 4162A4571011464700DFF3ED /* JSDedicatedWorkerGlobalScope.cpp in Sources */,
</span><span class="cx">                                 4162A454101145E300DFF3ED /* JSDedicatedWorkerGlobalScopeCustom.cpp in Sources */,
</span><ins>+                                F478755519983AFF0024A287 /* AxisScrollSnapAnimator.mm in Sources */,
</ins><span class="cx">                                 FDA15ED112B03F94003A583A /* JSDelayNode.cpp in Sources */,
</span><span class="cx">                                 31FB1A65120A5D3F00DC02A0 /* JSDeviceMotionEvent.cpp in Sources */,
</span><span class="cx">                                 31FB1A6C120A5D6900DC02A0 /* JSDeviceMotionEventCustom.cpp in Sources */,
</span></span></pre></div>
<a id="trunkSourceWebCorepagescrollingAxisScrollSnapOffsetsh"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h (172615 => 172616)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h        2014-08-15 00:18:25 UTC (rev 172615)
+++ trunk/Source/WebCore/page/scrolling/AxisScrollSnapOffsets.h        2014-08-15 00:21:25 UTC (rev 172616)
</span><span class="lines">@@ -28,7 +28,6 @@
</span><span class="cx"> 
</span><span class="cx"> #if ENABLE(CSS_SCROLL_SNAP)
</span><span class="cx"> 
</span><del>-#include &quot;LayoutUnit.h&quot;
</del><span class="cx"> #include &quot;ScrollTypes.h&quot;
</span><span class="cx"> #include &lt;wtf/PassOwnPtr.h&gt;
</span><span class="cx"> #include &lt;wtf/Vector.h&gt;
</span><span class="lines">@@ -42,6 +41,40 @@
</span><span class="cx"> 
</span><span class="cx"> void updateSnapOffsetsForScrollableArea(ScrollableArea&amp;, HTMLElement&amp; scrollingElement, const RenderBox&amp; scrollingElementBox, const RenderStyle&amp; scrollingElementStyle);
</span><span class="cx"> 
</span><ins>+template &lt;typename T&gt;
+T closestSnapOffset(const Vector&lt;T&gt;&amp; snapOffsets, T scrollDestination, float velocity)
+{
+    ASSERT(snapOffsets.size());
+    if (scrollDestination &lt;= snapOffsets.first())
+        return snapOffsets.first();
+
+    if (scrollDestination &gt;= snapOffsets.last())
+        return snapOffsets.last();
+
+    size_t lowerIndex = 0;
+    size_t upperIndex = snapOffsets.size() - 1;
+    while (lowerIndex &lt; upperIndex - 1) {
+        size_t middleIndex = (lowerIndex + upperIndex) / 2;
+        if (scrollDestination &lt; snapOffsets[middleIndex])
+            upperIndex = middleIndex;
+        else if (scrollDestination &gt; snapOffsets[middleIndex])
+            lowerIndex = middleIndex;
+        else {
+            upperIndex = middleIndex;
+            lowerIndex = middleIndex;
+            break;
+        }
+    }
+    T lowerSnapPosition = snapOffsets[lowerIndex];
+    T upperSnapPosition = snapOffsets[upperIndex];
+    // Nonzero velocity indicates a flick gesture. Even if another snap point is closer, snap to the one in the direction of the flick gesture.
+    if (velocity)
+        return velocity &lt; 0 ? lowerSnapPosition : upperSnapPosition;
+
+    bool isCloserToLowerSnapPosition = scrollDestination - lowerSnapPosition &lt;= upperSnapPosition - scrollDestination;
+    return isCloserToLowerSnapPosition ? lowerSnapPosition : upperSnapPosition;
+}
+
</ins><span class="cx"> } // namespace WebCore
</span><span class="cx"> 
</span><span class="cx"> #endif // CSS_SCROLL_SNAP
</span></span></pre></div>
<a id="trunkSourceWebCoreplatformmacAxisScrollSnapAnimatorh"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.h (0 => 172616)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.h                                (rev 0)
+++ trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.h        2014-08-15 00:21:25 UTC (rev 172616)
</span><span class="lines">@@ -0,0 +1,107 @@
</span><ins>+/*
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef AxisScrollSnapAnimator_h
+#define AxisScrollSnapAnimator_h
+
+#if ENABLE(CSS_SCROLL_SNAP)
+
+#include &quot;AxisScrollSnapOffsets.h&quot;
+#include &quot;LayoutUnit.h&quot;
+#include &quot;PlatformWheelEvent.h&quot;
+#include &quot;ScrollTypes.h&quot;
+
+namespace WebCore {
+
+enum class ScrollSnapState {
+    Snapping,
+    Gliding,
+    Idle
+};
+
+enum class WheelEventStatus {
+    UserScrollBegin,
+    UserScrolling,
+    UserScrollEnd,
+    InertialScrollBegin,
+    InertialScrolling,
+    InertialScrollEnd,
+    Unknown
+};
+
+class AxisScrollSnapAnimatorClient {
+protected:
+    virtual ~AxisScrollSnapAnimatorClient() { }
+
+public:
+    virtual LayoutUnit scrollOffsetInAxis(ScrollEventAxis) = 0;
+    virtual void immediateScrollInAxis(ScrollEventAxis, float velocity) = 0;
+    virtual void startScrollSnapTimer() = 0;
+    virtual void stopScrollSnapTimer() = 0;
+};
+
+class AxisScrollSnapAnimator {
+public:
+    AxisScrollSnapAnimator(AxisScrollSnapAnimatorClient*, Vector&lt;LayoutUnit&gt;* snapOffsets, ScrollEventAxis);
+    void handleWheelEvent(const PlatformWheelEvent&amp;);
+    bool shouldOverrideWheelEvent(const PlatformWheelEvent&amp;) const;
+    void scrollSnapAnimationUpdate();
+
+private:
+    void beginScrollSnapAnimation(ScrollSnapState);
+    void endScrollSnapAnimation();
+
+    float computeSnapDelta() const;
+    float computeGlideDelta() const;
+
+    void initializeGlideParameters(bool);
+    void pushInitialWheelDelta(float);
+    float averageInitialWheelDelta() const;
+    void clearInitialWheelDeltaWindow();
+
+    static const int wheelDeltaWindowSize = 3;
+
+    AxisScrollSnapAnimatorClient* m_client;
+    Vector&lt;LayoutUnit&gt;* m_snapOffsets;
+    ScrollEventAxis m_axis;
+    // Used to track both snapping and gliding behaviors.
+    ScrollSnapState m_currentState;
+    LayoutUnit m_initialOffset;
+    LayoutUnit m_targetOffset;
+    // Used to track gliding behavior.
+    LayoutUnit m_beginTrackingWheelDeltaOffset;
+    int m_numWheelDeltasTracked;
+    float m_wheelDeltaWindow[wheelDeltaWindowSize];
+    float m_glideMagnitude;
+    float m_glidePhaseShift;
+    float m_glideInitialWheelDelta;
+    bool m_shouldOverrideWheelEvent;
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(CSS_SCROLL_SNAP)
+
+#endif // AxisScrollSnapAnimator_h
</ins></span></pre></div>
<a id="trunkSourceWebCoreplatformmacAxisScrollSnapAnimatormm"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.mm (0 => 172616)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.mm                                (rev 0)
+++ trunk/Source/WebCore/platform/mac/AxisScrollSnapAnimator.mm        2014-08-15 00:21:25 UTC (rev 172616)
</span><span class="lines">@@ -0,0 +1,297 @@
</span><ins>+/*
+ * Copyright (C) 2014 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include &quot;config.h&quot;
+#include &quot;AxisScrollSnapAnimator.h&quot;
+
+#if ENABLE(CSS_SCROLL_SNAP)
+
+namespace WebCore {
+
+const float inertialScrollPredictionFactor = 16.7;
+const float snapMagnitudeMax = 25;
+const float snapMagnitudeMin = 5;
+const float snapThresholdHigh = 1000;
+const float snapThresholdLow = 50;
+const float glideBoostMultiplier = 3.5;
+const float maxTargetWheelDelta = 7;
+const float minTargetWheelDelta = 3.5;
+const float initialToFinalMomentumFactor = 1.0 / 40.0;
+
+static inline WheelEventStatus toWheelEventStatus(PlatformWheelEventPhase phase, PlatformWheelEventPhase momentumPhase)
+{
+    if (phase == PlatformWheelEventPhaseNone) {
+        switch (momentumPhase) {
+        case PlatformWheelEventPhaseBegan:
+            return WheelEventStatus::InertialScrollBegin;
+
+        case PlatformWheelEventPhaseChanged:
+            return WheelEventStatus::InertialScrolling;
+
+        case PlatformWheelEventPhaseEnded:
+            return WheelEventStatus::InertialScrollEnd;
+
+        default:
+            return WheelEventStatus::Unknown; 
+        }
+    }
+    if (momentumPhase == PlatformWheelEventPhaseNone) {
+        switch (phase) {
+        case PlatformWheelEventPhaseBegan:
+            return WheelEventStatus::UserScrollBegin;
+
+        case PlatformWheelEventPhaseChanged:
+            return WheelEventStatus::UserScrolling;
+
+        case PlatformWheelEventPhaseEnded:
+            return WheelEventStatus::UserScrollEnd;
+
+        default:
+            return WheelEventStatus::Unknown;
+        }
+    }
+    return WheelEventStatus::Unknown;
+}
+
+static inline float projectedInertialScrollDistance(float initialWheelDelta)
+{
+    // FIXME: Experiments with inertial scrolling show a fairly consistent linear relationship between initial wheel delta and total distance scrolled.
+    // In the future, we'll want to find a more accurate way of inertial scroll prediction.
+    return inertialScrollPredictionFactor * initialWheelDelta;
+}
+
+AxisScrollSnapAnimator::AxisScrollSnapAnimator(AxisScrollSnapAnimatorClient* client, Vector&lt;LayoutUnit&gt;* snapOffsets, ScrollEventAxis axis)
+    : m_client(client)
+    , m_snapOffsets(snapOffsets)
+    , m_axis(axis)
+    , m_currentState(ScrollSnapState::Idle)
+    , m_initialOffset(0)
+    , m_targetOffset(0)
+    , m_beginTrackingWheelDeltaOffset(0)
+    , m_numWheelDeltasTracked(0)
+    , m_glideMagnitude(0)
+    , m_glidePhaseShift(0)
+    , m_glideInitialWheelDelta(0)
+    , m_shouldOverrideWheelEvent(false)
+{
+}
+
+void AxisScrollSnapAnimator::handleWheelEvent(const PlatformWheelEvent&amp; event)
+{
+    float wheelDelta = m_axis == ScrollEventAxis::Horizontal ? -event.deltaX() : -event.deltaY();
+    WheelEventStatus wheelStatus = toWheelEventStatus(event.phase(), event.momentumPhase());
+    if ((wheelStatus == WheelEventStatus::UserScrolling || wheelStatus == WheelEventStatus::InertialScrolling || wheelStatus == WheelEventStatus::InertialScrollBegin) &amp;&amp; !wheelDelta)
+        return;
+
+    switch (wheelStatus) {
+    case WheelEventStatus::UserScrollBegin:
+    case WheelEventStatus::UserScrolling:
+        endScrollSnapAnimation();
+        break;
+
+    case WheelEventStatus::UserScrollEnd:
+        beginScrollSnapAnimation(ScrollSnapState::Snapping);
+        break;
+
+    case WheelEventStatus::InertialScrollBegin:
+        // Begin tracking wheel deltas for glide prediction.
+        endScrollSnapAnimation();
+        pushInitialWheelDelta(wheelDelta);
+        m_beginTrackingWheelDeltaOffset = m_client-&gt;scrollOffsetInAxis(m_axis);
+        break;
+
+    case WheelEventStatus::InertialScrolling:
+        if (m_currentState != ScrollSnapState::Gliding) {
+            // FIXME: Investigate why small wheel deltas pushed into the window without the &lt; -1, &gt; 1 check.
+            if (m_numWheelDeltasTracked &lt; wheelDeltaWindowSize &amp;&amp; (wheelDelta &lt; -1 || wheelDelta &gt; 1))
+                pushInitialWheelDelta(wheelDelta);
+
+            if (m_numWheelDeltasTracked == wheelDeltaWindowSize)
+                beginScrollSnapAnimation(ScrollSnapState::Gliding);
+        }
+        break;
+
+    case WheelEventStatus::InertialScrollEnd:
+        clearInitialWheelDeltaWindow();
+        m_shouldOverrideWheelEvent = false;
+        break;
+
+    case WheelEventStatus::Unknown:
+        ASSERT_NOT_REACHED();
+        break;
+    }
+}
+
+bool AxisScrollSnapAnimator::shouldOverrideWheelEvent(const PlatformWheelEvent&amp; event) const
+{
+    return m_shouldOverrideWheelEvent &amp;&amp; toWheelEventStatus(event.phase(), event.momentumPhase()) == WheelEventStatus::InertialScrolling;
+}
+
+void AxisScrollSnapAnimator::scrollSnapAnimationUpdate()
+{
+    ASSERT(m_currentState == ScrollSnapState::Gliding || m_currentState == ScrollSnapState::Snapping);
+    float delta = m_currentState == ScrollSnapState::Snapping ? computeSnapDelta() : computeGlideDelta();
+    if (delta)
+        m_client-&gt;immediateScrollInAxis(m_axis, delta);
+    else {
+        endScrollSnapAnimation();
+        m_currentState = ScrollSnapState::Idle;
+    }
+}
+
+void AxisScrollSnapAnimator::beginScrollSnapAnimation(ScrollSnapState newState)
+{
+    ASSERT(newState == ScrollSnapState::Gliding || newState == ScrollSnapState::Snapping);
+    LayoutUnit offset = m_client-&gt;scrollOffsetInAxis(m_axis);
+    float initialWheelDelta = newState == ScrollSnapState::Gliding ? averageInitialWheelDelta() : 0;
+    LayoutUnit projectedScrollDestination = newState == ScrollSnapState::Gliding ? m_beginTrackingWheelDeltaOffset + LayoutUnit(projectedInertialScrollDistance(initialWheelDelta)) : offset;
+    projectedScrollDestination = std::min(std::max(projectedScrollDestination, m_snapOffsets-&gt;first()), m_snapOffsets-&gt;last());
+    m_initialOffset = offset;
+    m_targetOffset = closestSnapOffset&lt;LayoutUnit&gt;(*m_snapOffsets, projectedScrollDestination, initialWheelDelta);
+    if (m_initialOffset == m_targetOffset)
+        return;
+
+    m_currentState = newState;
+    if (newState == ScrollSnapState::Gliding) {
+        m_shouldOverrideWheelEvent = true;
+        m_glideInitialWheelDelta = initialWheelDelta;
+        bool glideRequiresBoost;
+        if (initialWheelDelta &gt; 0)
+            glideRequiresBoost = projectedScrollDestination - offset &lt; m_targetOffset - projectedScrollDestination;
+        else
+            glideRequiresBoost = offset - projectedScrollDestination &lt; projectedScrollDestination - m_targetOffset;
+
+        initializeGlideParameters(glideRequiresBoost);
+        clearInitialWheelDeltaWindow();
+    }
+    m_client-&gt;startScrollSnapTimer();
+}
+
+void AxisScrollSnapAnimator::endScrollSnapAnimation()
+{
+    if (m_currentState == ScrollSnapState::Gliding)
+        clearInitialWheelDeltaWindow();
+
+    m_currentState = ScrollSnapState::Idle;
+    m_client-&gt;stopScrollSnapTimer();
+}
+
+// Computes the amount to scroll by when performing a &quot;snap&quot; operation, i.e. when a user releases the trackpad without flicking. The snap delta
+// is a function of progress t, where t is equal to DISTANCE_TRAVELED / TOTAL_DISTANCE, DISTANCE_TRAVELED is the distance from the initialOffset
+// to the current offset, and TOTAL_DISTANCE is the distance from initialOffset to targetOffset. The snapping equation is as follows:
+// delta(t) = MAGNITUDE * sin(PI * t). MAGNITUDE indicates the top speed reached near the middle of the animation (t = 0.5), and is a linear
+// relationship of the distance traveled, clamped by arbitrary min and max values.
+float AxisScrollSnapAnimator::computeSnapDelta() const
+{
+    LayoutUnit offset = m_client-&gt;scrollOffsetInAxis(m_axis);
+    bool canComputeSnap =  (m_initialOffset &lt;= offset &amp;&amp; offset &lt; m_targetOffset) || (m_targetOffset &lt; offset &amp;&amp; offset &lt;= m_initialOffset);
+    if (m_currentState != ScrollSnapState::Snapping || !canComputeSnap)
+        return 0;
+
+    int sign = m_initialOffset &lt; m_targetOffset ? 1 : -1;
+    float progress = ((float)(offset - m_initialOffset)) / (m_targetOffset - m_initialOffset);
+    // Threshold the distance before computing magnitude, so only distances within a certain range are considered.
+    float thresholdedDistance = std::min(std::max&lt;float&gt;((m_targetOffset - m_initialOffset) * sign, snapThresholdLow), snapThresholdHigh);
+    float magnitude = snapMagnitudeMin + (snapMagnitudeMax - snapMagnitudeMin) * (thresholdedDistance - snapThresholdLow) / (snapThresholdHigh - snapThresholdLow);
+    float rawSnapAmount = std::max&lt;float&gt;(1, magnitude * sin(piFloat * progress));
+    if ((m_targetOffset &lt; offset &amp;&amp; offset - rawSnapAmount &lt; m_targetOffset) || (m_targetOffset &gt; offset &amp;&amp; offset + rawSnapAmount &gt; m_targetOffset))
+        return m_targetOffset - offset;
+
+    return sign * rawSnapAmount;
+}
+
+// Computes the amount to scroll by when performing a &quot;glide&quot; operation, i.e. when a user releases the trackpad with an initial velocity. Here,
+// we want the scroll offset to animate directly to the snap point. The snap delta is a function of progress t, where t is equal to
+// DISTANCE_TRAVELED / TOTAL_DISTANCE, DISTANCE_TRAVELED is the distance from the initialOffset to the current offset, and TOTAL_DISTANCE is
+// the distance from initialOffset to targetOffset.
+// The general model of our gliding equation is delta(t) = MAGNITUDE * (1 + cos(PI * t + PHASE_SHIFT)). This was determined after examining the
+// momentum velocity curve as a function of progress. To compute MAGNITUDE and PHASE_SHIFT, we use initial velocity V0 and the final velocity VF,
+// both as wheel deltas (pixels per timestep). VF should be a small value (&lt; 10) chosen based on the initial velocity and TOTAL_DISTANCE.
+// We also enforce the following constraints for the gliding equation:
+//   1. delta(0) = V0, since we want the initial velocity of the gliding animation to match the user's scroll velocity. The exception to this is
+//      when the glide velocity is not enough to naturally reach the next snap point, and thus requires a boost (see initializeGlideParameters)
+//   2. delta(1) = VF, since at t=1, the animation has completed and we want the last wheel delta to match the final velocity VF. Note that this
+//      doesn't guarantee that the final velocity will be exactly VF. However, assuming that the initial velocity is much less than TOTAL_DISTANCE,
+//      the last wheel delta will be very close, if not the same, as VF.
+// For MAGNITUDE = (V0 + VF) / 2 and PHASE_SHIFT = arccos((V0 - VF) / (V0 + VF)), observe that delta(0) and delta(1) evaluate respectively to V0
+// and VF. Thus, we can express our gliding equation all in terms of V0, VF and t.
+float AxisScrollSnapAnimator::computeGlideDelta() const
+{
+    LayoutUnit offset = m_client-&gt;scrollOffsetInAxis(m_axis);
+    bool canComputeGlide = (m_initialOffset &lt;= offset &amp;&amp; offset &lt; m_targetOffset) || (m_targetOffset &lt; offset &amp;&amp; offset &lt;= m_initialOffset);
+    if (m_currentState != ScrollSnapState::Gliding || !canComputeGlide)
+        return 0;
+
+    float progress = ((float)(offset - m_initialOffset)) / (m_targetOffset - m_initialOffset);
+    float shift = ceil(m_glideMagnitude * (1 + cos(piFloat * progress + m_glidePhaseShift)));
+    shift = m_initialOffset &lt; m_targetOffset ? std::max&lt;float&gt;(shift, 1) : std::min&lt;float&gt;(shift, -1);
+    if ((m_initialOffset &lt; m_targetOffset &amp;&amp; offset + shift &gt; m_targetOffset) || (m_initialOffset &gt; m_targetOffset &amp;&amp; offset + shift &lt; m_targetOffset))
+        return m_targetOffset - offset;
+
+    return shift;
+}
+
+void AxisScrollSnapAnimator::initializeGlideParameters(bool shouldIncreaseInitialWheelDelta)
+{
+    // FIXME: Glide boost is a hacky way to speed up natural scrolling velocity. We should find a better way to accomplish this.
+    if (shouldIncreaseInitialWheelDelta)
+        m_glideInitialWheelDelta *= glideBoostMultiplier;
+
+    // FIXME: There must be a better way to determine a good target delta than multiplying by a factor and clamping to min/max values.
+    float targetFinalWheelDelta = initialToFinalMomentumFactor * (m_glideInitialWheelDelta &lt; 0 ? -m_glideInitialWheelDelta : m_glideInitialWheelDelta);
+    targetFinalWheelDelta = (m_glideInitialWheelDelta &gt; 0 ? 1 : -1) * std::min(std::max(targetFinalWheelDelta, minTargetWheelDelta), maxTargetWheelDelta);
+    m_glideMagnitude = (m_glideInitialWheelDelta + targetFinalWheelDelta) / 2;
+    m_glidePhaseShift = acos((m_glideInitialWheelDelta - targetFinalWheelDelta) / (m_glideInitialWheelDelta + targetFinalWheelDelta));
+}
+
+void AxisScrollSnapAnimator::pushInitialWheelDelta(float wheelDelta)
+{
+    if (m_numWheelDeltasTracked &lt; wheelDeltaWindowSize)
+        m_wheelDeltaWindow[m_numWheelDeltasTracked++] = wheelDelta;
+}
+
+float AxisScrollSnapAnimator::averageInitialWheelDelta() const
+{
+    if (!m_numWheelDeltasTracked)
+        return 0;
+
+    float sum = 0;
+    for (int i = 0; i &lt; m_numWheelDeltasTracked; i++)
+        sum += m_wheelDeltaWindow[i];
+
+    return sum / m_numWheelDeltasTracked;
+}
+
+void AxisScrollSnapAnimator::clearInitialWheelDeltaWindow()
+{
+    for (int i = 0; i &lt; m_numWheelDeltasTracked; i++)
+        m_wheelDeltaWindow[i] = 0;
+
+    m_numWheelDeltasTracked = 0;
+}
+
+} // namespace WebCore
+
+#endif // CSS_SCROLL_SNAP
</ins></span></pre>
</div>
</div>

</body>
</html>