Mac: Add rails for scrolling.

Add a MouseWheelRailsFilter structure to compute the rail mode for
input events, based on some simple hysteresis.

Make InputHandlerProxy honor the rails requests of MouseWheel events.
This is not an ideal long-term implementation -- the rails should be
passed to cc::InputHandler, so that they can be handled correctly by
other event types. This version is being used so that it may be merged.

BUG=468454

Review URL: https://codereview.chromium.org/1019153002

Cr-Commit-Position: refs/heads/master@{#321738}
diff --git a/content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.cc b/content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.cc
new file mode 100644
index 0000000..3681ba9
--- /dev/null
+++ b/content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.cc
@@ -0,0 +1,38 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h"
+
+using blink::WebInputEvent;
+using blink::WebMouseWheelEvent;
+
+namespace content {
+
+MouseWheelRailsFilterMac::MouseWheelRailsFilterMac() {
+}
+
+MouseWheelRailsFilterMac::~MouseWheelRailsFilterMac() {
+}
+
+WebInputEvent::RailsMode MouseWheelRailsFilterMac::UpdateRailsMode(
+    const WebMouseWheelEvent& event) {
+  // A somewhat-arbitrary decay constant for hysteresis.
+  const float kDecayConstant = 0.8f;
+
+  if (event.phase == WebMouseWheelEvent::PhaseBegan) {
+    decayed_delta_ = gfx::Vector2dF();
+  }
+  if (event.deltaX == 0 && event.deltaY == 0)
+    return WebInputEvent::RailsModeFree;
+
+  decayed_delta_.Scale(kDecayConstant);
+  decayed_delta_ +=
+      gfx::Vector2dF(std::abs(event.deltaX), std::abs(event.deltaY));
+
+  if (decayed_delta_.y() >= decayed_delta_.x())
+    return WebInputEvent::RailsModeVertical;
+  return WebInputEvent::RailsModeHorizontal;
+}
+
+}  // namespace content
diff --git a/content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h b/content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h
new file mode 100644
index 0000000..ffed162c9
--- /dev/null
+++ b/content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h
@@ -0,0 +1,27 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_MOUSE_WHEEL_RAILS_FILTER_MAC_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_MOUSE_WHEEL_RAILS_FILTER_MAC_H_
+
+#include "content/common/content_export.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace content {
+
+class CONTENT_EXPORT MouseWheelRailsFilterMac {
+ public:
+  MouseWheelRailsFilterMac();
+  ~MouseWheelRailsFilterMac();
+  blink::WebInputEvent::RailsMode UpdateRailsMode(
+      const blink::WebMouseWheelEvent& event);
+
+ private:
+  gfx::Vector2dF decayed_delta_;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_INPUT_MOUSE_WHEEL_RAILS_FILTER_MAC_H_
diff --git a/content/browser/renderer_host/input/mouse_wheel_rails_filter_unittest_mac.cc b/content/browser/renderer_host/input/mouse_wheel_rails_filter_unittest_mac.cc
new file mode 100644
index 0000000..7811ef7
--- /dev/null
+++ b/content/browser/renderer_host/input/mouse_wheel_rails_filter_unittest_mac.cc
@@ -0,0 +1,64 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using blink::WebInputEvent;
+using blink::WebMouseWheelEvent;
+
+namespace content {
+namespace {
+
+WebMouseWheelEvent MakeEvent(WebMouseWheelEvent::Phase phase,
+                             float delta_x,
+                             float delta_y) {
+  WebMouseWheelEvent event;
+  event.phase = phase;
+  event.deltaX = delta_x;
+  event.deltaY = delta_y;
+  return event;
+}
+
+TEST(MouseWheelRailsFilterMacTest, Functionality) {
+  WebInputEvent::RailsMode mode;
+  MouseWheelRailsFilterMac filter;
+
+  // Start with a mostly-horizontal event and see that it is locked
+  // horizontally and continues to be locked.
+  mode =
+      filter.UpdateRailsMode(MakeEvent(WebMouseWheelEvent::PhaseBegan, 2, 1));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeHorizontal);
+  mode = filter.UpdateRailsMode(
+      MakeEvent(WebMouseWheelEvent::PhaseChanged, 2, 2));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeHorizontal);
+  mode = filter.UpdateRailsMode(
+      MakeEvent(WebMouseWheelEvent::PhaseChanged, 10, -4));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeHorizontal);
+
+  // Change from horizontal to vertical and back.
+  mode =
+      filter.UpdateRailsMode(MakeEvent(WebMouseWheelEvent::PhaseBegan, 4, 1));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeHorizontal);
+  mode = filter.UpdateRailsMode(
+      MakeEvent(WebMouseWheelEvent::PhaseChanged, 3, 4));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeHorizontal);
+  mode = filter.UpdateRailsMode(
+      MakeEvent(WebMouseWheelEvent::PhaseChanged, 1, 4));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeVertical);
+  mode = filter.UpdateRailsMode(
+      MakeEvent(WebMouseWheelEvent::PhaseChanged, 10, 0));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeHorizontal);
+
+  // Make sure zeroes don't break things.
+  mode = filter.UpdateRailsMode(
+      MakeEvent(WebMouseWheelEvent::PhaseChanged, 0, 0));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeFree);
+  mode =
+      filter.UpdateRailsMode(MakeEvent(WebMouseWheelEvent::PhaseBegan, 0, 0));
+  EXPECT_EQ(mode, WebInputEvent::RailsModeFree);
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.h b/content/browser/renderer_host/render_widget_host_view_mac.h
index 3f3fc1e..2465dd1 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.h
+++ b/content/browser/renderer_host/render_widget_host_view_mac.h
@@ -21,6 +21,7 @@
 #include "content/browser/compositor/browser_compositor_view_mac.h"
 #include "content/browser/compositor/delegated_frame_host.h"
 #include "content/browser/renderer_host/display_link_mac.h"
+#include "content/browser/renderer_host/input/mouse_wheel_rails_filter_mac.h"
 #include "content/browser/renderer_host/render_widget_host_view_base.h"
 #include "content/common/content_export.h"
 #include "content/common/cursors/webcursor.h"
@@ -35,6 +36,7 @@
 #include "ui/gfx/display_observer.h"
 
 namespace content {
+class RenderWidgetHostImpl;
 class RenderWidgetHostViewMac;
 class RenderWidgetHostViewMacEditCommandHelper;
 class WebContents;
@@ -171,6 +173,10 @@
   // key up events yet.
   // Used for filtering out non-matching NSKeyUp events.
   std::set<unsigned short> keyDownCodes_;
+
+  // The filter used to guide touch events towards a horizontal or vertical
+  // orientation.
+  content::MouseWheelRailsFilterMac mouseWheelFilter_;
 }
 
 @property(nonatomic, readonly) NSRange selectedRange;
@@ -199,7 +205,6 @@
 @end
 
 namespace content {
-class RenderWidgetHostImpl;
 
 ///////////////////////////////////////////////////////////////////////////////
 // RenderWidgetHostViewMac
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 7f0cb00..71bb4038 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -2254,8 +2254,9 @@
     // Allow rubber-banding in both directions.
     bool canRubberbandLeft = true;
     bool canRubberbandRight = true;
-    const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
+    WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
         event, self, canRubberbandLeft, canRubberbandRight);
+    webEvent.railsMode = mouseWheelFilter_.UpdateRailsMode(webEvent);
     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
   }
 
@@ -2377,8 +2378,9 @@
   if (renderWidgetHostView_->render_widget_host_) {
     BOOL canRubberbandLeft = [responderDelegate_ canRubberbandLeft:self];
     BOOL canRubberbandRight = [responderDelegate_ canRubberbandRight:self];
-    const WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
+    WebMouseWheelEvent webEvent = WebInputEventFactory::mouseWheelEvent(
         event, self, canRubberbandLeft, canRubberbandRight);
+    webEvent.railsMode = mouseWheelFilter_.UpdateRailsMode(webEvent);
     renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(webEvent);
   }
 }
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index 9db87bff..94ca3117 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -1096,6 +1096,8 @@
       'browser/renderer_host/input/motion_event_android.h',
       'browser/renderer_host/input/motion_event_web.cc',
       'browser/renderer_host/input/motion_event_web.h',
+      'browser/renderer_host/input/mouse_wheel_rails_filter_mac.cc',
+      'browser/renderer_host/input/mouse_wheel_rails_filter_mac.h',
       'browser/renderer_host/input/stylus_text_selector.cc',
       'browser/renderer_host/input/stylus_text_selector.h',
       'browser/renderer_host/input/synthetic_gesture.cc',
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index f80176c6..7f628e6 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -497,6 +497,7 @@
       'browser/renderer_host/input/mock_input_ack_handler.h',
       'browser/renderer_host/input/mock_input_router_client.cc',
       'browser/renderer_host/input/mock_input_router_client.h',
+      'browser/renderer_host/input/mouse_wheel_rails_filter_unittest_mac.cc',
       'browser/renderer_host/input/stylus_text_selector_unittest.cc',
       'browser/renderer_host/input/synthetic_gesture_controller_unittest.cc',
       'browser/renderer_host/input/tap_suppression_controller_unittest.cc',
diff --git a/content/renderer/input/input_handler_proxy.cc b/content/renderer/input/input_handler_proxy.cc
index f9cc75b9..57f425d 100644
--- a/content/renderer/input/input_handler_proxy.cc
+++ b/content/renderer/input/input_handler_proxy.cc
@@ -332,6 +332,16 @@
   InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE;
   cc::InputHandlerScrollResult scroll_result;
 
+  // TODO(ccameron): The rail information should be pushed down into
+  // InputHandler.
+  gfx::Vector2dF scroll_delta(
+      wheel_event.railsMode != WebInputEvent::RailsModeVertical
+          ? -wheel_event.deltaX
+          : 0,
+      wheel_event.railsMode != WebInputEvent::RailsModeHorizontal
+          ? -wheel_event.deltaY
+          : 0);
+
   if (wheel_event.scrollByPage) {
     // TODO(jamesr): We don't properly handle scroll by page in the compositor
     // thread, so punt it to the main thread. http://crbug.com/236639
@@ -342,9 +352,8 @@
     result = DID_NOT_HANDLE;
   } else if (smooth_scroll_enabled_) {
     cc::InputHandler::ScrollStatus scroll_status =
-        input_handler_->ScrollAnimated(
-            gfx::Point(wheel_event.x, wheel_event.y),
-            gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY));
+        input_handler_->ScrollAnimated(gfx::Point(wheel_event.x, wheel_event.y),
+                                       scroll_delta);
     switch (scroll_status) {
       case cc::InputHandler::SCROLL_STARTED:
         result = DID_HANDLE;
@@ -360,12 +369,11 @@
         gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::WHEEL);
     switch (scroll_status) {
       case cc::InputHandler::SCROLL_STARTED: {
-        TRACE_EVENT_INSTANT2(
-            "input", "InputHandlerProxy::handle_input wheel scroll",
-            TRACE_EVENT_SCOPE_THREAD, "deltaX", -wheel_event.deltaX, "deltaY",
-            -wheel_event.deltaY);
+        TRACE_EVENT_INSTANT2("input",
+                             "InputHandlerProxy::handle_input wheel scroll",
+                             TRACE_EVENT_SCOPE_THREAD, "deltaX",
+                             scroll_delta.x(), "deltaY", scroll_delta.y());
         gfx::Point scroll_point(wheel_event.x, wheel_event.y);
-        gfx::Vector2dF scroll_delta(-wheel_event.deltaX, -wheel_event.deltaY);
         scroll_result = input_handler_->ScrollBy(scroll_point, scroll_delta);
         HandleOverscroll(scroll_point, scroll_result);
         input_handler_->ScrollEnd();