[chromium] Convert screen space scroll gestures to layer space
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Aug 2012 11:01:41 +0000 (11:01 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 20 Aug 2012 11:01:41 +0000 (11:01 +0000)
https://bugs.webkit.org/show_bug.cgi?id=92499

Patch by Sami Kyostila <skyostil@chromium.org> on 2012-08-20
Reviewed by James Robinson.

Source/WebCore:

Scroll gestures should be converted from screen space to local layer space to
correctly apply the scroll delta to page scaled and/or transformed layers.
Visually this means that the scrolled content will always follow the user's
finger for any "well-formed" layer transform.

Wheel scroll deltas will still be directly interpreted as local layer scroll
coordinates.

We also adjust the logic for propagating ("bubbling") scroll events to parent
layers. Previously a parent layer was allowed to scroll in the screen-space
axis orthogonal to the direction the starting layer scrolled toward. For
example if a vertically scrolling layer is scrolled diagonally down and right,
the layer moves down and its parent to the right.

This patch generalizes this behavior to non-axis aligned transformed layers so
that the scrolling direction of any parent is constrained to be perpendicular
direction of movement of the starting layer. This makes the scrolling behavior
of transformed layers physically plausible. For instance, assume a 45 degree
rotated, vertically scrollable layer. Dragging your finger vertically
(relative to the layer) scrolls the layer up and down, while horizontal
movement results in the parent of the layer moving in a corresponding way.

Since generally users want to scroll a single layer in one direction, this
patch also introduces a rule that if the resulting movement of a layer is
within 45 degrees of the original scroll input, the bubbling process is
stopped. This makes it possible to reliably scroll a single layer without
affecting any of its parents.

Added new unit tests:
    CCLayerTreeHostImplTest.scrollAxisAlignedRotatedLayer
    CCLayerTreeHostImplTest.scrollNonAxisAlignedRotatedLayer
    CCLayerTreeHostImplTest.scrollScaledLayer
    CCMathUtilTest.smallestAngleBetweenVectors
    CCMathUtilTest.vectorProjection

* platform/graphics/chromium/cc/CCInputHandler.h:
* platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp:
(WebCore::CCLayerTreeHostImpl::CCLayerTreeHostImpl):
(WebCore::CCLayerTreeHostImpl::scrollBegin):
(WebCore::scrollLayerWithScreenSpaceDelta):
(WebCore):
(WebCore::scrollLayerWithLocalDelta):
(WebCore::CCLayerTreeHostImpl::scrollBy):
* platform/graphics/chromium/cc/CCLayerTreeHostImpl.h:
(CCLayerTreeHostImpl):
* platform/graphics/chromium/cc/CCMathUtil.cpp:
(WebCore::CCMathUtil::smallestAngleBetweenVectors):
(WebCore):
(WebCore::CCMathUtil::projectVector):
* platform/graphics/chromium/cc/CCMathUtil.h:
(CCMathUtil):

Source/WebKit/chromium:

Added new tests for verifying transformed layer scrolling:

    CCLayerTreeHostImplTest.scrollAxisAlignedRotatedLayer
    CCLayerTreeHostImplTest.scrollNonAxisAlignedRotatedLayer
    CCLayerTreeHostImplTest.scrollScaledLayer

Also some tests for the introduced math utilities:

    CCMathUtilTest.smallestAngleBetweenVectors
    CCMathUtilTest.vectorProjection

* src/WebCompositorInputHandlerImpl.cpp:
(WebKit::WebCompositorInputHandlerImpl::handleInputEventInternal):
(WebKit::WebCompositorInputHandlerImpl::handleGestureFling):
* tests/CCLayerTreeHostImplTest.cpp: Adjusted scroll delta in scrollChildBeyondLimit to avoid triggering the 45 degree rule.
* tests/CCLayerTreeHostTest.cpp:
* tests/CCMathUtilTest.cpp:
* tests/WebCompositorInputHandlerImplTest.cpp:
(MockCCInputHandlerClient):
(WebKit::TEST_F):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@126018 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
Source/WebCore/ChangeLog
Source/WebCore/platform/graphics/chromium/cc/CCInputHandler.h
Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp
Source/WebCore/platform/graphics/chromium/cc/CCLayerTreeHostImpl.h
Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.cpp
Source/WebCore/platform/graphics/chromium/cc/CCMathUtil.h
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/src/WebCompositorInputHandlerImpl.cpp
Source/WebKit/chromium/tests/CCLayerTreeHostImplTest.cpp
Source/WebKit/chromium/tests/CCLayerTreeHostTest.cpp
Source/WebKit/chromium/tests/CCMathUtilTest.cpp
Source/WebKit/chromium/tests/WebCompositorInputHandlerImplTest.cpp

index 47a5d8d..06890e5 100644 (file)
@@ -1,3 +1,62 @@
+2012-08-20  Sami Kyostila  <skyostil@chromium.org>
+
+        [chromium] Convert screen space scroll gestures to layer space
+        https://bugs.webkit.org/show_bug.cgi?id=92499
+
+        Reviewed by James Robinson.
+
+        Scroll gestures should be converted from screen space to local layer space to
+        correctly apply the scroll delta to page scaled and/or transformed layers.
+        Visually this means that the scrolled content will always follow the user's
+        finger for any "well-formed" layer transform.
+
+        Wheel scroll deltas will still be directly interpreted as local layer scroll
+        coordinates.
+
+        We also adjust the logic for propagating ("bubbling") scroll events to parent
+        layers. Previously a parent layer was allowed to scroll in the screen-space
+        axis orthogonal to the direction the starting layer scrolled toward. For
+        example if a vertically scrolling layer is scrolled diagonally down and right,
+        the layer moves down and its parent to the right.
+
+        This patch generalizes this behavior to non-axis aligned transformed layers so
+        that the scrolling direction of any parent is constrained to be perpendicular
+        direction of movement of the starting layer. This makes the scrolling behavior
+        of transformed layers physically plausible. For instance, assume a 45 degree
+        rotated, vertically scrollable layer. Dragging your finger vertically
+        (relative to the layer) scrolls the layer up and down, while horizontal
+        movement results in the parent of the layer moving in a corresponding way.
+
+        Since generally users want to scroll a single layer in one direction, this
+        patch also introduces a rule that if the resulting movement of a layer is
+        within 45 degrees of the original scroll input, the bubbling process is
+        stopped. This makes it possible to reliably scroll a single layer without
+        affecting any of its parents.
+
+        Added new unit tests:
+            CCLayerTreeHostImplTest.scrollAxisAlignedRotatedLayer
+            CCLayerTreeHostImplTest.scrollNonAxisAlignedRotatedLayer
+            CCLayerTreeHostImplTest.scrollScaledLayer
+            CCMathUtilTest.smallestAngleBetweenVectors
+            CCMathUtilTest.vectorProjection
+
+        * platform/graphics/chromium/cc/CCInputHandler.h:
+        * platform/graphics/chromium/cc/CCLayerTreeHostImpl.cpp:
+        (WebCore::CCLayerTreeHostImpl::CCLayerTreeHostImpl):
+        (WebCore::CCLayerTreeHostImpl::scrollBegin):
+        (WebCore::scrollLayerWithScreenSpaceDelta):
+        (WebCore):
+        (WebCore::scrollLayerWithLocalDelta):
+        (WebCore::CCLayerTreeHostImpl::scrollBy):
+        * platform/graphics/chromium/cc/CCLayerTreeHostImpl.h:
+        (CCLayerTreeHostImpl):
+        * platform/graphics/chromium/cc/CCMathUtil.cpp:
+        (WebCore::CCMathUtil::smallestAngleBetweenVectors):
+        (WebCore):
+        (WebCore::CCMathUtil::projectVector):
+        * platform/graphics/chromium/cc/CCMathUtil.h:
+        (CCMathUtil):
+
 2012-08-20  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK] Add API to set preferred languages to WebKit2 GTK+
index 77dd2db..d4f3394 100644 (file)
@@ -48,21 +48,21 @@ public:
     enum ScrollStatus { ScrollOnMainThread, ScrollStarted, ScrollIgnored };
     enum ScrollInputType { Gesture, Wheel };
 
-    // Attempt to start scrolling a layer at a given point in window
-    // coordinates. Returns ScrollStarted if the layer at the coordinates can
-    // be scrolled, ScrollOnMainThread if the scroll event should instead be
-    // delegated to the main thread, or ScrollIgnored if there is nothing to
-    // be scrolled at the given coordinates.
+    // Selects a layer to be scrolled at a given point in window coordinates.
+    // Returns ScrollStarted if the layer at the coordinates can be scrolled,
+    // ScrollOnMainThread if the scroll event should instead be delegated to the
+    // main thread, or ScrollIgnored if there is nothing to be scrolled at the
+    // given coordinates.
     virtual ScrollStatus scrollBegin(const IntPoint&, ScrollInputType) = 0;
 
-    // Scroll the layer selected with scrollBegin(). If there is no room to
-    // move the layer in the requested direction, its first ancestor layer that
-    // can be scrolled will be moved instead. Should only be called if
-    // scrollBegin() returned ScrollStarted.
-    virtual void scrollBy(const IntSize&) = 0;
-
-    // Stop scrolling the layer selected with scrollBegin(). Should only be
+    // Scroll the selected layer starting at the given window coordinate. If
+    // there is no room to move the layer in the requested direction, its first
+    // ancestor layer that can be scrolled will be moved instead. Should only be
     // called if scrollBegin() returned ScrollStarted.
+    virtual void scrollBy(const IntPoint&, const IntSize&) = 0;
+
+    // Stop scrolling the selected layer. Should only be called if scrollBegin()
+    // returned ScrollStarted.
     virtual void scrollEnd() = 0;
 
     virtual void pinchGestureBegin() = 0;
index a5138ea..1bccf7c 100644 (file)
@@ -35,6 +35,7 @@
 #include "CCLayerIterator.h"
 #include "CCLayerTreeHost.h"
 #include "CCLayerTreeHostCommon.h"
+#include "CCMathUtil.h"
 #include "CCOverdrawMetrics.h"
 #include "CCPageScaleAnimation.h"
 #include "CCPrioritizedTextureManager.h"
@@ -119,6 +120,7 @@ CCLayerTreeHostImpl::CCLayerTreeHostImpl(const CCLayerTreeSettings& settings, CC
     , m_rootScrollLayerImpl(0)
     , m_currentlyScrollingLayerImpl(0)
     , m_scrollingLayerIdFromPreviousTree(-1)
+    , m_scrollDeltaIsInScreenSpace(false)
     , m_settings(settings)
     , m_deviceScaleFactor(1)
     , m_visible(true)
@@ -888,36 +890,95 @@ CCInputHandlerClient::ScrollStatus CCLayerTreeHostImpl::scrollBegin(const IntPoi
 
     if (potentiallyScrollingLayerImpl) {
         m_currentlyScrollingLayerImpl = potentiallyScrollingLayerImpl;
+        // Gesture events need to be transformed from screen coordinates to local layer coordinates
+        // so that the scrolling contents exactly follow the user's finger. In contrast, wheel
+        // events are already in local layer coordinates so we can just apply them directly.
+        m_scrollDeltaIsInScreenSpace = (type == Gesture);
         return ScrollStarted;
     }
     return ScrollIgnored;
 }
 
-void CCLayerTreeHostImpl::scrollBy(const IntSize& scrollDelta)
+static FloatSize scrollLayerWithScreenSpaceDelta(CCLayerImpl& layerImpl, const FloatPoint& screenSpacePoint, const FloatSize& screenSpaceDelta)
+{
+    // Layers with non-invertible screen space transforms should not have passed the scroll hit
+    // test in the first place.
+    ASSERT(layerImpl.screenSpaceTransform().isInvertible());
+    WebTransformationMatrix inverseScreenSpaceTransform = layerImpl.screenSpaceTransform().inverse();
+
+    // First project the scroll start and end points to local layer space to find the scroll delta
+    // in layer coordinates.
+    bool startClipped, endClipped;
+    FloatPoint screenSpaceEndPoint = screenSpacePoint + screenSpaceDelta;
+    FloatPoint localStartPoint = CCMathUtil::projectPoint(inverseScreenSpaceTransform, screenSpacePoint, startClipped);
+    FloatPoint localEndPoint = CCMathUtil::projectPoint(inverseScreenSpaceTransform, screenSpaceEndPoint, endClipped);
+
+    // In general scroll point coordinates should not get clipped.
+    ASSERT(!startClipped);
+    ASSERT(!endClipped);
+    if (startClipped || endClipped)
+        return FloatSize();
+
+    // Apply the scroll delta.
+    FloatSize previousDelta(layerImpl.scrollDelta());
+    layerImpl.scrollBy(localEndPoint - localStartPoint);
+
+    // Calculate the applied scroll delta in screen space coordinates.
+    FloatPoint actualLocalEndPoint = localStartPoint + layerImpl.scrollDelta() - previousDelta;
+    FloatPoint actualScreenSpaceEndPoint = CCMathUtil::mapPoint(layerImpl.screenSpaceTransform(), actualLocalEndPoint, endClipped);
+    ASSERT(!endClipped);
+    if (endClipped)
+        return FloatSize();
+    return actualScreenSpaceEndPoint - screenSpacePoint;
+}
+
+static FloatSize scrollLayerWithLocalDelta(CCLayerImpl& layerImpl, const FloatSize& localDelta)
+{
+    FloatSize previousDelta(layerImpl.scrollDelta());
+    layerImpl.scrollBy(localDelta);
+    return layerImpl.scrollDelta() - previousDelta;
+}
+
+void CCLayerTreeHostImpl::scrollBy(const IntPoint& viewportPoint, const IntSize& scrollDelta)
 {
     TRACE_EVENT0("cc", "CCLayerTreeHostImpl::scrollBy");
     if (!m_currentlyScrollingLayerImpl)
         return;
 
     FloatSize pendingDelta(scrollDelta);
-    pendingDelta.scale(1 / m_pageScaleDelta);
-
-    for (CCLayerImpl* layerImpl = m_currentlyScrollingLayerImpl; layerImpl && !pendingDelta.isZero(); layerImpl = layerImpl->parent()) {
+    for (CCLayerImpl* layerImpl = m_currentlyScrollingLayerImpl; layerImpl; layerImpl = layerImpl->parent()) {
         if (!layerImpl->scrollable())
             continue;
-        FloatSize previousDelta(layerImpl->scrollDelta());
-        layerImpl->scrollBy(pendingDelta);
-        // Reset the pending scroll delta to zero if the layer was able to move along the requested
-        // axis. This is to ensure it is possible to scroll exactly to the beginning or end of a
-        // scroll area regardless of the scroll step. For diagonal scrolls this also avoids applying
-        // the scroll on one axis to multiple layers.
-        if (previousDelta.width() != layerImpl->scrollDelta().width())
-            pendingDelta.setWidth(0);
-        if (previousDelta.height() != layerImpl->scrollDelta().height())
-            pendingDelta.setHeight(0);
+
+        FloatSize appliedDelta;
+        if (m_scrollDeltaIsInScreenSpace)
+            appliedDelta = scrollLayerWithScreenSpaceDelta(*layerImpl, viewportPoint, pendingDelta);
+        else
+            appliedDelta = scrollLayerWithLocalDelta(*layerImpl, pendingDelta);
+
+        // If the layer wasn't able to move, try the next one in the hierarchy.
+        float moveThresholdSquared = 0.1 * 0.1;
+        if (appliedDelta.diagonalLengthSquared() < moveThresholdSquared)
+            continue;
+
+        // If the applied delta is within 45 degrees of the input delta, bail out to make it easier
+        // to scroll just one layer in one direction without affecting any of its parents.
+        float angleThreshold = 45;
+        if (CCMathUtil::smallestAngleBetweenVectors(appliedDelta, pendingDelta) < angleThreshold) {
+            pendingDelta = FloatSize();
+            break;
+        }
+
+        // Allow further movement only on an axis perpendicular to the direction in which the layer
+        // moved.
+        FloatSize perpendicularAxis(-appliedDelta.height(), appliedDelta.width());
+        pendingDelta = CCMathUtil::projectVector(pendingDelta, perpendicularAxis);
+
+        if (flooredIntSize(pendingDelta).isZero())
+            break;
     }
 
-    if (!scrollDelta.isZero() && pendingDelta.isEmpty()) {
+    if (!scrollDelta.isZero() && flooredIntSize(pendingDelta).isEmpty()) {
         m_client->setNeedsCommitOnImplThread();
         m_client->setNeedsRedrawOnImplThread();
     }
index 8a8685f..6083f5f 100644 (file)
@@ -74,7 +74,7 @@ public:
 
     // CCInputHandlerClient implementation
     virtual CCInputHandlerClient::ScrollStatus scrollBegin(const IntPoint&, CCInputHandlerClient::ScrollInputType) OVERRIDE;
-    virtual void scrollBy(const IntSize&) OVERRIDE;
+    virtual void scrollBy(const IntPoint&, const IntSize&) OVERRIDE;
     virtual void scrollEnd() OVERRIDE;
     virtual void pinchGestureBegin() OVERRIDE;
     virtual void pinchGestureUpdate(float, const IntPoint&) OVERRIDE;
@@ -268,6 +268,7 @@ private:
     CCLayerImpl* m_rootScrollLayerImpl;
     CCLayerImpl* m_currentlyScrollingLayerImpl;
     int m_scrollingLayerIdFromPreviousTree;
+    bool m_scrollDeltaIsInScreenSpace;
     CCLayerTreeSettings m_settings;
     IntSize m_layoutViewportSize;
     IntSize m_deviceViewportSize;
index b378ce3..cad0a5d 100644 (file)
@@ -381,4 +381,19 @@ void CCMathUtil::flattenTransformTo2d(WebTransformationMatrix& transform)
     transform.setM43(0);
 }
 
+float CCMathUtil::smallestAngleBetweenVectors(const FloatSize& v1, const FloatSize& v2)
+{
+    float dotProduct = (v1.width() * v2.width() + v1.height() * v2.height()) / (v1.diagonalLength() * v2.diagonalLength());
+    // Clamp to compensate for rounding errors.
+    dotProduct = std::max(-1.f, std::min(1.f, dotProduct));
+    return rad2deg(acosf(dotProduct));
+}
+
+FloatSize CCMathUtil::projectVector(const FloatSize& source, const FloatSize& destination)
+{
+    float sourceDotDestination = source.width() * destination.width() + source.height() * destination.height();
+    float projectedLength = sourceDotDestination / destination.diagonalLengthSquared();
+    return FloatSize(projectedLength * destination.width(), projectedLength * destination.height());
+}
+
 } // namespace WebCore
index 3fb5914..0f9e7a0 100644 (file)
@@ -115,6 +115,13 @@ public:
     static FloatPoint projectPoint(const WebKit::WebTransformationMatrix&, const FloatPoint&, bool& clipped);
 
     static void flattenTransformTo2d(WebKit::WebTransformationMatrix&);
+
+    // Returns the smallest angle between the given two vectors in degrees. Neither vector is
+    // assumed to be normalized.
+    static float smallestAngleBetweenVectors(const FloatSize&, const FloatSize&);
+
+    // Projects the |source| vector onto |destination|. Neither vector is assumed to be normalized.
+    static FloatSize projectVector(const FloatSize& source, const FloatSize& destination);
 };
 
 } // namespace WebCore
index e4bb50c..d6bf653 100644 (file)
@@ -1,3 +1,31 @@
+2012-08-20  Sami Kyostila  <skyostil@chromium.org>
+
+        [chromium] Convert screen space scroll gestures to layer space
+        https://bugs.webkit.org/show_bug.cgi?id=92499
+
+        Reviewed by James Robinson.
+
+        Added new tests for verifying transformed layer scrolling:
+
+            CCLayerTreeHostImplTest.scrollAxisAlignedRotatedLayer
+            CCLayerTreeHostImplTest.scrollNonAxisAlignedRotatedLayer
+            CCLayerTreeHostImplTest.scrollScaledLayer
+
+        Also some tests for the introduced math utilities:
+
+            CCMathUtilTest.smallestAngleBetweenVectors
+            CCMathUtilTest.vectorProjection
+
+        * src/WebCompositorInputHandlerImpl.cpp:
+        (WebKit::WebCompositorInputHandlerImpl::handleInputEventInternal):
+        (WebKit::WebCompositorInputHandlerImpl::handleGestureFling):
+        * tests/CCLayerTreeHostImplTest.cpp: Adjusted scroll delta in scrollChildBeyondLimit to avoid triggering the 45 degree rule.
+        * tests/CCLayerTreeHostTest.cpp:
+        * tests/CCMathUtilTest.cpp:
+        * tests/WebCompositorInputHandlerImplTest.cpp:
+        (MockCCInputHandlerClient):
+        (WebKit::TEST_F):
+
 2012-08-20  Pavel Feldman  <pfeldman@chromium.org>
 
         Web Inspector: load scripts panel lazily
index 2f79bd8..8084d71 100644 (file)
@@ -181,7 +181,7 @@ WebCompositorInputHandlerImpl::EventDisposition WebCompositorInputHandlerImpl::h
         switch (scrollStatus) {
         case CCInputHandlerClient::ScrollStarted: {
             TRACE_EVENT_INSTANT2("cc", "WebCompositorInputHandlerImpl::handleInput wheel scroll", "deltaX", -wheelEvent.deltaX, "deltaY", -wheelEvent.deltaY);
-            m_inputHandlerClient->scrollBy(IntSize(-wheelEvent.deltaX, -wheelEvent.deltaY));
+            m_inputHandlerClient->scrollBy(IntPoint(wheelEvent.x, wheelEvent.y), IntSize(-wheelEvent.deltaX, -wheelEvent.deltaY));
             m_inputHandlerClient->scrollEnd();
             return DidHandle;
         }
@@ -216,7 +216,7 @@ WebCompositorInputHandlerImpl::EventDisposition WebCompositorInputHandlerImpl::h
             return DidNotHandle;
 
         const WebGestureEvent& gestureEvent = *static_cast<const WebGestureEvent*>(&event);
-        m_inputHandlerClient->scrollBy(IntSize(-gestureEvent.deltaX, -gestureEvent.deltaY));
+        m_inputHandlerClient->scrollBy(IntPoint(gestureEvent.x, gestureEvent.y), IntSize(-gestureEvent.deltaX, -gestureEvent.deltaY));
         return DidHandle;
     } else if (event.type == WebInputEvent::GestureScrollEnd) {
         ASSERT(m_expectScrollUpdateEnd);
@@ -263,7 +263,7 @@ WebCompositorInputHandlerImpl::EventDisposition WebCompositorInputHandlerImpl::h
 
 WebCompositorInputHandlerImpl::EventDisposition WebCompositorInputHandlerImpl::handleGestureFling(const WebGestureEvent& gestureEvent)
 {
-    CCInputHandlerClient::ScrollStatus scrollStatus = m_inputHandlerClient->scrollBegin(IntPoint(gestureEvent.x, gestureEvent.y), CCInputHandlerClient::Wheel);
+    CCInputHandlerClient::ScrollStatus scrollStatus = m_inputHandlerClient->scrollBegin(IntPoint(gestureEvent.x, gestureEvent.y), CCInputHandlerClient::Gesture);
     switch (scrollStatus) {
     case CCInputHandlerClient::ScrollStarted: {
         TRACE_EVENT_INSTANT0("cc", "WebCompositorInputHandlerImpl::handleGestureFling::started");
index c85a6b8..6ec0ce5 100644 (file)
@@ -154,12 +154,11 @@ public:
         m_hostImpl->setRootLayer(root.release());
     }
 
-    static PassOwnPtr<CCLayerImpl> createScrollableLayer(int id, const FloatPoint& position, const IntSize& size)
+    static PassOwnPtr<CCLayerImpl> createScrollableLayer(int id, const IntSize& size)
     {
         OwnPtr<CCLayerImpl> layer = CCLayerImpl::create(id);
         layer->setScrollable(true);
         layer->setDrawsContent(true);
-        layer->setPosition(position);
         layer->setBounds(size);
         layer->setContentBounds(size);
         layer->setMaxScrollPosition(IntSize(size.width() * 2, size.height() * 2));
@@ -269,7 +268,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollRootCallsCommitAndRedraw)
     initializeLayerRendererAndDrawFrame();
 
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(IntSize(0, 10));
+    m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10));
     m_hostImpl->scrollEnd();
     EXPECT_TRUE(m_didRequestRedraw);
     EXPECT_TRUE(m_didRequestCommit);
@@ -312,7 +311,7 @@ TEST_F(CCLayerTreeHostImplTest, replaceTreeWhileScrolling)
 
     // We should still be scrolling, because the scrolled layer also exists in the new tree.
     IntSize scrollDelta(0, 10);
-    m_hostImpl->scrollBy(scrollDelta);
+    m_hostImpl->scrollBy(IntPoint(), scrollDelta);
     m_hostImpl->scrollEnd();
     OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
     expectContains(*scrollInfo, scrollLayerId, scrollDelta);
@@ -374,10 +373,10 @@ TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionBasic)
 
     // All scroll types outside this region should succeed.
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(75, 75), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(IntSize(0, 10));
+    m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10));
     m_hostImpl->scrollEnd();
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(75, 75), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(IntSize(0, 10));
+    m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10));
     m_hostImpl->scrollEnd();
 }
 
@@ -393,7 +392,7 @@ TEST_F(CCLayerTreeHostImplTest, nonFastScrollableRegionWithOffset)
 
     // This point would fall into the non-fast scrollable region except that we've moved the layer down by 25 pixels.
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(40, 10), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(IntSize(0, 1));
+    m_hostImpl->scrollBy(IntPoint(), IntSize(0, 1));
     m_hostImpl->scrollEnd();
 
     // This point is still inside the non-fast region.
@@ -615,7 +614,7 @@ TEST_F(CCLayerTreeHostImplTest, inhibitScrollAndPageScaleUpdatesWhileAnimatingPa
     // Scrolling during the animation is ignored.
     const IntSize scrollDelta(0, 10);
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(25, 25), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(scrollDelta);
+    m_hostImpl->scrollBy(IntPoint(), scrollDelta);
     m_hostImpl->scrollEnd();
 
     // The final page scale and scroll deltas should match what we got
@@ -886,7 +885,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollNonCompositedRoot)
     initializeLayerRendererAndDrawFrame();
 
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(IntSize(0, 10));
+    m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10));
     m_hostImpl->scrollEnd();
     EXPECT_TRUE(m_didRequestRedraw);
     EXPECT_TRUE(m_didRequestCommit);
@@ -898,13 +897,13 @@ TEST_F(CCLayerTreeHostImplTest, scrollChildCallsCommitAndRedraw)
     OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1);
     root->setBounds(surfaceSize);
     root->setContentBounds(surfaceSize);
-    root->addChild(createScrollableLayer(2, FloatPoint(0, 0), surfaceSize));
+    root->addChild(createScrollableLayer(2, surfaceSize));
     m_hostImpl->setRootLayer(root.release());
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
     initializeLayerRendererAndDrawFrame();
 
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(IntSize(0, 10));
+    m_hostImpl->scrollBy(IntPoint(), IntSize(0, 10));
     m_hostImpl->scrollEnd();
     EXPECT_TRUE(m_didRequestRedraw);
     EXPECT_TRUE(m_didRequestCommit);
@@ -914,7 +913,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollMissesChild)
 {
     IntSize surfaceSize(10, 10);
     OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1);
-    root->addChild(createScrollableLayer(2, FloatPoint(0, 0), surfaceSize));
+    root->addChild(createScrollableLayer(2, surfaceSize));
     m_hostImpl->setRootLayer(root.release());
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
     initializeLayerRendererAndDrawFrame();
@@ -929,7 +928,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollMissesBackfacingChild)
 {
     IntSize surfaceSize(10, 10);
     OwnPtr<CCLayerImpl> root = CCLayerImpl::create(1);
-    OwnPtr<CCLayerImpl> child = createScrollableLayer(2, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> child = createScrollableLayer(2, surfaceSize);
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
 
     WebTransformationMatrix matrix;
@@ -951,11 +950,11 @@ TEST_F(CCLayerTreeHostImplTest, scrollMissesBackfacingChild)
 TEST_F(CCLayerTreeHostImplTest, scrollBlockedByContentLayer)
 {
     IntSize surfaceSize(10, 10);
-    OwnPtr<CCLayerImpl> contentLayer = createScrollableLayer(1, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> contentLayer = createScrollableLayer(1, surfaceSize);
     contentLayer->setShouldScrollOnMainThread(true);
     contentLayer->setScrollable(false);
 
-    OwnPtr<CCLayerImpl> scrollLayer = createScrollableLayer(2, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> scrollLayer = createScrollableLayer(2, surfaceSize);
     scrollLayer->addChild(contentLayer.release());
 
     m_hostImpl->setRootLayer(scrollLayer.release());
@@ -970,7 +969,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnMainThread)
 {
     IntSize surfaceSize(10, 10);
     float pageScale = 2;
-    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize);
     m_hostImpl->setRootLayer(root.release());
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
     initializeLayerRendererAndDrawFrame();
@@ -979,7 +978,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnMainThread)
     IntSize expectedScrollDelta(scrollDelta);
     IntSize expectedMaxScroll(m_hostImpl->rootLayer()->maxScrollPosition());
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(scrollDelta);
+    m_hostImpl->scrollBy(IntPoint(), scrollDelta);
     m_hostImpl->scrollEnd();
 
     // Set new page scale from main thread.
@@ -1001,7 +1000,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnImplThread)
 {
     IntSize surfaceSize(10, 10);
     float pageScale = 2;
-    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize);
     m_hostImpl->setRootLayer(root.release());
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
     m_hostImpl->setPageScaleFactorAndLimits(1, 1, pageScale);
@@ -1011,7 +1010,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollRootAndChangePageScaleOnImplThread)
     IntSize expectedScrollDelta(scrollDelta);
     IntSize expectedMaxScroll(m_hostImpl->rootLayer()->maxScrollPosition());
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(scrollDelta);
+    m_hostImpl->scrollBy(IntPoint(), scrollDelta);
     m_hostImpl->scrollEnd();
 
     // Set new page scale on impl thread by pinching.
@@ -1041,7 +1040,7 @@ TEST_F(CCLayerTreeHostImplTest, pageScaleDeltaAppliedToRootScrollLayerOnly)
     CCLayerImpl* root = m_hostImpl->rootLayer();
     CCLayerImpl* child = root->children()[0].get();
 
-    OwnPtr<CCLayerImpl> scrollableChild = createScrollableLayer(3, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> scrollableChild = createScrollableLayer(3, surfaceSize);
     child->addChild(scrollableChild.release());
     CCLayerImpl* grandChild = child->children()[0].get();
 
@@ -1079,7 +1078,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollChildAndChangePageScaleOnMainThread)
     // Also mark the root scrollable so it becomes the root scroll layer.
     root->setScrollable(true);
     int scrollLayerId = 2;
-    root->addChild(createScrollableLayer(scrollLayerId, FloatPoint(0, 0), surfaceSize));
+    root->addChild(createScrollableLayer(scrollLayerId, surfaceSize));
     m_hostImpl->setRootLayer(root.release());
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
     initializeLayerRendererAndDrawFrame();
@@ -1090,7 +1089,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollChildAndChangePageScaleOnMainThread)
     IntSize expectedScrollDelta(scrollDelta);
     IntSize expectedMaxScroll(child->maxScrollPosition());
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-    m_hostImpl->scrollBy(scrollDelta);
+    m_hostImpl->scrollBy(IntPoint(), scrollDelta);
     m_hostImpl->scrollEnd();
 
     float pageScale = 2;
@@ -1114,12 +1113,12 @@ TEST_F(CCLayerTreeHostImplTest, scrollChildBeyondLimit)
     // parent layer is scrolled on the axis on which the child was unable to
     // scroll.
     IntSize surfaceSize(10, 10);
-    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize);
 
-    OwnPtr<CCLayerImpl> grandChild = createScrollableLayer(3, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> grandChild = createScrollableLayer(3, surfaceSize);
     grandChild->setScrollPosition(IntPoint(0, 5));
 
-    OwnPtr<CCLayerImpl> child = createScrollableLayer(2, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> child = createScrollableLayer(2, surfaceSize);
     child->setScrollPosition(IntPoint(3, 0));
     child->addChild(grandChild.release());
 
@@ -1128,9 +1127,9 @@ TEST_F(CCLayerTreeHostImplTest, scrollChildBeyondLimit)
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
     initializeLayerRendererAndDrawFrame();
     {
-        IntSize scrollDelta(-3, -7);
+        IntSize scrollDelta(-8, -7);
         EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-        m_hostImpl->scrollBy(scrollDelta);
+        m_hostImpl->scrollBy(IntPoint(), scrollDelta);
         m_hostImpl->scrollEnd();
 
         OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
@@ -1150,8 +1149,8 @@ TEST_F(CCLayerTreeHostImplTest, scrollEventBubbling)
     // When we try to scroll a non-scrollable child layer, the scroll delta
     // should be applied to one of its ancestors if possible.
     IntSize surfaceSize(10, 10);
-    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, FloatPoint(0, 0), surfaceSize);
-    OwnPtr<CCLayerImpl> child = createScrollableLayer(2, FloatPoint(0, 0), surfaceSize);
+    OwnPtr<CCLayerImpl> root = createScrollableLayer(1, surfaceSize);
+    OwnPtr<CCLayerImpl> child = createScrollableLayer(2, surfaceSize);
 
     child->setScrollable(false);
     root->addChild(child.release());
@@ -1162,7 +1161,7 @@ TEST_F(CCLayerTreeHostImplTest, scrollEventBubbling)
     {
         IntSize scrollDelta(0, 4);
         EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-        m_hostImpl->scrollBy(scrollDelta);
+        m_hostImpl->scrollBy(IntPoint(), scrollDelta);
         m_hostImpl->scrollEnd();
 
         OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
@@ -1176,18 +1175,150 @@ TEST_F(CCLayerTreeHostImplTest, scrollEventBubbling)
 TEST_F(CCLayerTreeHostImplTest, scrollBeforeRedraw)
 {
     IntSize surfaceSize(10, 10);
-    m_hostImpl->setRootLayer(createScrollableLayer(1, FloatPoint(0, 0), surfaceSize));
+    m_hostImpl->setRootLayer(createScrollableLayer(1, surfaceSize));
     m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
 
     // Draw one frame and then immediately rebuild the layer tree to mimic a tree synchronization.
     initializeLayerRendererAndDrawFrame();
     m_hostImpl->detachLayerTree();
-    m_hostImpl->setRootLayer(createScrollableLayer(2, FloatPoint(0, 0), surfaceSize));
+    m_hostImpl->setRootLayer(createScrollableLayer(2, surfaceSize));
 
     // Scrolling should still work even though we did not draw yet.
     EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
 }
 
+TEST_F(CCLayerTreeHostImplTest, scrollAxisAlignedRotatedLayer)
+{
+    setupScrollAndContentsLayers(IntSize(100, 100));
+
+    // Rotate the root layer 90 degrees counter-clockwise about its center.
+    WebTransformationMatrix rotateTransform;
+    rotateTransform.rotate(-90);
+    m_hostImpl->rootLayer()->setTransform(rotateTransform);
+
+    IntSize surfaceSize(50, 50);
+    m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
+    initializeLayerRendererAndDrawFrame();
+
+    // Scroll to the right in screen coordinates with a gesture.
+    IntSize gestureScrollDelta(10, 0);
+    EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted);
+    m_hostImpl->scrollBy(IntPoint(), gestureScrollDelta);
+    m_hostImpl->scrollEnd();
+
+    // The layer should have scrolled down in its local coordinates.
+    OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
+    expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), IntSize(0, gestureScrollDelta.width()));
+
+    // Reset and scroll down with the wheel.
+    m_hostImpl->rootLayer()->setScrollDelta(FloatSize());
+    IntSize wheelScrollDelta(0, 10);
+    EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
+    m_hostImpl->scrollBy(IntPoint(), wheelScrollDelta);
+    m_hostImpl->scrollEnd();
+
+    // The layer should have scrolled down in its local coordinates.
+    scrollInfo = m_hostImpl->processScrollDeltas();
+    expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), wheelScrollDelta);
+}
+
+TEST_F(CCLayerTreeHostImplTest, scrollNonAxisAlignedRotatedLayer)
+{
+    setupScrollAndContentsLayers(IntSize(100, 100));
+    int childLayerId = 3;
+    float childLayerAngle = -20;
+
+    // Create a child layer that is rotated to a non-axis-aligned angle.
+    OwnPtr<CCLayerImpl> child = createScrollableLayer(childLayerId, m_hostImpl->rootLayer()->contentBounds());
+    WebTransformationMatrix rotateTransform;
+    rotateTransform.translate(-50, -50);
+    rotateTransform.rotate(childLayerAngle);
+    rotateTransform.translate(50, 50);
+    child->setTransform(rotateTransform);
+
+    // Only allow vertical scrolling.
+    child->setMaxScrollPosition(IntSize(0, child->contentBounds().height()));
+    m_hostImpl->rootLayer()->addChild(child.release());
+
+    IntSize surfaceSize(50, 50);
+    m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
+    initializeLayerRendererAndDrawFrame();
+
+    {
+        // Scroll down in screen coordinates with a gesture.
+        IntSize gestureScrollDelta(0, 10);
+        EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted);
+        m_hostImpl->scrollBy(IntPoint(), gestureScrollDelta);
+        m_hostImpl->scrollEnd();
+
+        // The child layer should have scrolled down in its local coordinates an amount proportional to
+        // the angle between it and the input scroll delta.
+        IntSize expectedScrollDelta(0, gestureScrollDelta.height() * cosf(deg2rad(childLayerAngle)));
+        OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
+        expectContains(*scrollInfo.get(), childLayerId, expectedScrollDelta);
+
+        // The root layer should not have scrolled, because the input delta was close to the layer's
+        // axis of movement.
+        EXPECT_EQ(scrollInfo->scrolls.size(), 1u);
+    }
+
+    {
+        // Now reset and scroll the same amount horizontally.
+        m_hostImpl->rootLayer()->children()[1]->setScrollDelta(FloatSize());
+        IntSize gestureScrollDelta(10, 0);
+        EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted);
+        m_hostImpl->scrollBy(IntPoint(), gestureScrollDelta);
+        m_hostImpl->scrollEnd();
+
+        // The child layer should have scrolled down in its local coordinates an amount proportional to
+        // the angle between it and the input scroll delta.
+        IntSize expectedScrollDelta(0, -gestureScrollDelta.width() * sinf(deg2rad(childLayerAngle)));
+        OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
+        expectContains(*scrollInfo.get(), childLayerId, expectedScrollDelta);
+
+        // The root layer should have scrolled more, since the input scroll delta was mostly
+        // orthogonal to the child layer's vertical scroll axis.
+        IntSize expectedRootScrollDelta(gestureScrollDelta.width() * pow(cosf(deg2rad(childLayerAngle)), 2), 0);
+        expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), expectedRootScrollDelta);
+    }
+}
+
+TEST_F(CCLayerTreeHostImplTest, scrollScaledLayer)
+{
+    setupScrollAndContentsLayers(IntSize(100, 100));
+
+    // Scale the layer to twice its normal size.
+    int scale = 2;
+    WebTransformationMatrix scaleTransform;
+    scaleTransform.scale(scale);
+    m_hostImpl->rootLayer()->setTransform(scaleTransform);
+
+    IntSize surfaceSize(50, 50);
+    m_hostImpl->setViewportSize(surfaceSize, surfaceSize);
+    initializeLayerRendererAndDrawFrame();
+
+    // Scroll down in screen coordinates with a gesture.
+    IntSize scrollDelta(0, 10);
+    EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Gesture), CCInputHandlerClient::ScrollStarted);
+    m_hostImpl->scrollBy(IntPoint(), scrollDelta);
+    m_hostImpl->scrollEnd();
+
+    // The layer should have scrolled down in its local coordinates, but half he amount.
+    OwnPtr<CCScrollAndScaleSet> scrollInfo = m_hostImpl->processScrollDeltas();
+    expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), IntSize(0, scrollDelta.height() / scale));
+
+    // Reset and scroll down with the wheel.
+    m_hostImpl->rootLayer()->setScrollDelta(FloatSize());
+    IntSize wheelScrollDelta(0, 10);
+    EXPECT_EQ(m_hostImpl->scrollBegin(IntPoint(0, 0), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
+    m_hostImpl->scrollBy(IntPoint(), wheelScrollDelta);
+    m_hostImpl->scrollEnd();
+
+    // The scale should not have been applied to the scroll delta.
+    scrollInfo = m_hostImpl->processScrollDeltas();
+    expectContains(*scrollInfo.get(), m_hostImpl->rootLayer()->id(), wheelScrollDelta);
+}
+
 class BlendStateTrackerContext: public FakeWebGraphicsContext3D {
 public:
     BlendStateTrackerContext() : m_blend(false) { }
index 86c8ae6..5202208 100644 (file)
@@ -2221,7 +2221,7 @@ public:
     {
         if (impl->sourceAnimationFrameNumber() == 1) {
             EXPECT_EQ(impl->scrollBegin(IntPoint(5, 5), CCInputHandlerClient::Wheel), CCInputHandlerClient::ScrollStarted);
-            impl->scrollBy(m_scrollAmount);
+            impl->scrollBy(IntPoint(), m_scrollAmount);
             impl->scrollEnd();
         } else if (impl->sourceAnimationFrameNumber() == 2)
             endTest();
index 7c5d5a3..2a29b05 100644 (file)
@@ -152,4 +152,51 @@ TEST(CCMathUtilTest, verifyEnclosingRectOfVerticesUsesCorrectInitialBounds)
     EXPECT_FLOAT_RECT_EQ(FloatRect(FloatPoint(-100, -100), FloatSize(90, 90)), result);
 }
 
+TEST(CCMathUtilTest, smallestAngleBetweenVectors)
+{
+    FloatSize x(1, 0);
+    FloatSize y(0, 1);
+    FloatSize testVector(0.5, 0.5);
+
+    // Orthogonal vectors are at an angle of 90 degress.
+    EXPECT_EQ(90, CCMathUtil::smallestAngleBetweenVectors(x, y));
+
+    // A vector makes a zero angle with itself.
+    EXPECT_EQ(0, CCMathUtil::smallestAngleBetweenVectors(x, x));
+    EXPECT_EQ(0, CCMathUtil::smallestAngleBetweenVectors(y, y));
+    EXPECT_EQ(0, CCMathUtil::smallestAngleBetweenVectors(testVector, testVector));
+
+    // Parallel but reversed vectors are at 180 degrees.
+    EXPECT_EQ(180, CCMathUtil::smallestAngleBetweenVectors(x, -x));
+    EXPECT_EQ(180, CCMathUtil::smallestAngleBetweenVectors(y, -y));
+    EXPECT_EQ(180, CCMathUtil::smallestAngleBetweenVectors(testVector, -testVector));
+
+    // The test vector is at a known angle.
+    EXPECT_EQ(45, floor(CCMathUtil::smallestAngleBetweenVectors(testVector, x)));
+    EXPECT_EQ(45, floor(CCMathUtil::smallestAngleBetweenVectors(testVector, y)));
+}
+
+TEST(CCMathUtilTest, vectorProjection)
+{
+    FloatSize x(1, 0);
+    FloatSize y(0, 1);
+    FloatSize testVector(0.3, 0.7);
+
+    // Orthogonal vectors project to a zero vector.
+    EXPECT_EQ(FloatSize(0, 0), CCMathUtil::projectVector(x, y));
+    EXPECT_EQ(FloatSize(0, 0), CCMathUtil::projectVector(y, x));
+
+    // Projecting a vector onto the orthonormal basis gives the corresponding component of the
+    // vector.
+    EXPECT_EQ(FloatSize(testVector.width(), 0), CCMathUtil::projectVector(testVector, x));
+    EXPECT_EQ(FloatSize(0, testVector.height()), CCMathUtil::projectVector(testVector, y));
+
+    // Finally check than an arbitrary vector projected to another one gives a vector parallel to
+    // the second vector.
+    FloatSize targetVector(0.5, 0.2);
+    FloatSize projectedVector = CCMathUtil::projectVector(testVector, targetVector);
+    EXPECT_EQ(projectedVector.width() / targetVector.width(),
+              projectedVector.height() / targetVector.height());
+}
+
 } // namespace
index 36e1b86..8313b0f 100644 (file)
@@ -60,7 +60,7 @@ public:
     MOCK_METHOD0(scheduleAnimation, void());
 
     MOCK_METHOD2(scrollBegin, ScrollStatus(const WebCore::IntPoint&, WebCore::CCInputHandlerClient::ScrollInputType));
-    MOCK_METHOD1(scrollBy, void(const WebCore::IntSize&));
+    MOCK_METHOD2(scrollBy, void(const WebCore::IntPoint&, const WebCore::IntSize&));
     MOCK_METHOD0(scrollEnd, void());
 
 private:
@@ -201,7 +201,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureScrollStarted)
 
     gesture.type = WebInputEvent::GestureScrollUpdate;
     gesture.deltaY = -40; // -Y means scroll down - i.e. in the +Y direction.
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::Property(&WebCore::IntSize::height, testing::Gt(0))));
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::Property(&WebCore::IntSize::height, testing::Gt(0))));
     m_inputHandler->handleInputEvent(gesture);
 
     VERIFY_AND_RESET_MOCKS();
@@ -386,7 +386,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureFlingAnimates)
     EXPECT_CALL(m_mockCCInputHandlerClient, scheduleAnimation());
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollBegin(testing::_, testing::_))
         .WillOnce(testing::Return(WebCore::CCInputHandlerClient::ScrollStarted));
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::Property(&WebCore::IntSize::width, testing::Lt(0))));
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::Property(&WebCore::IntSize::width, testing::Lt(0))));
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollEnd());
     m_inputHandler->animate(10.1);
 
@@ -398,7 +398,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureFlingAnimates)
     EXPECT_CALL(m_mockCCInputHandlerClient, scheduleAnimation());
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollBegin(testing::_, testing::_))
         .WillOnce(testing::Return(WebCore::CCInputHandlerClient::ScrollOnMainThread));
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_)).Times(0);
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::_)).Times(0);
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollEnd()).Times(0);
 
     // Expected wheel fling animation parameters:
@@ -468,7 +468,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureFlingTransferResets)
     EXPECT_CALL(m_mockCCInputHandlerClient, scheduleAnimation());
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollBegin(testing::_, testing::_))
         .WillOnce(testing::Return(WebCore::CCInputHandlerClient::ScrollStarted));
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::Property(&WebCore::IntSize::width, testing::Lt(0))));
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::Property(&WebCore::IntSize::width, testing::Lt(0))));
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollEnd());
     m_inputHandler->animate(10.1);
 
@@ -480,7 +480,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureFlingTransferResets)
     EXPECT_CALL(m_mockCCInputHandlerClient, scheduleAnimation());
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollBegin(testing::_, testing::_))
         .WillOnce(testing::Return(WebCore::CCInputHandlerClient::ScrollOnMainThread));
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_)).Times(0);
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::_)).Times(0);
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollEnd()).Times(0);
 
     // Expected wheel fling animation parameters:
@@ -547,7 +547,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureFlingTransferResets)
     EXPECT_CALL(m_mockCCInputHandlerClient, scheduleAnimation());
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollBegin(testing::_, testing::_))
         .WillOnce(testing::Return(WebCore::CCInputHandlerClient::ScrollStarted));
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::Property(&WebCore::IntSize::height, testing::Gt(0))));
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::Property(&WebCore::IntSize::height, testing::Gt(0))));
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollEnd());
     m_inputHandler->animate(30.1);
 
@@ -557,7 +557,7 @@ TEST_F(WebCompositorInputHandlerImplTest, gestureFlingTransferResets)
     EXPECT_CALL(m_mockCCInputHandlerClient, scheduleAnimation());
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollBegin(testing::_, testing::_))
         .WillOnce(testing::Return(WebCore::CCInputHandlerClient::ScrollOnMainThread));
-    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_)).Times(0);
+    EXPECT_CALL(m_mockCCInputHandlerClient, scrollBy(testing::_, testing::_)).Times(0);
     EXPECT_CALL(m_mockCCInputHandlerClient, scrollEnd()).Times(0);
 
     // We should get parameters from the second fling, nothing from the first fling should "leak".