Implemented smooth scrolling using xinput2 in X11.

Adds support for Xinput 2.1 smooth scrolling for hardware that supports it
(such as touchpads and some mice). This provides similar behaviour to that seen
on Mac OS X.

BUG=384970

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

Cr-Commit-Position: refs/heads/master@{#368645}
diff --git a/chrome/browser/ui/views/frame/browser_root_view.cc b/chrome/browser/ui/views/frame/browser_root_view.cc
index 8eaba17..5bf11e51 100644
--- a/chrome/browser/ui/views/frame/browser_root_view.cc
+++ b/chrome/browser/ui/views/frame/browser_root_view.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/views/frame/browser_root_view.h"
 
+#include <cmath>
+
 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h"
 #include "chrome/browser/defaults.h"
 #include "chrome/browser/profiles/profile.h"
@@ -28,7 +30,9 @@
                                  views::Widget* widget)
     : views::internal::RootView(widget),
       browser_view_(browser_view),
-      forwarding_to_tab_strip_(false) { }
+      forwarding_to_tab_strip_(false),
+      scroll_remainder_x_(0),
+      scroll_remainder_y_(0) {}
 
 bool BrowserRootView::GetDropFormats(
       int* formats,
@@ -130,19 +134,39 @@
     if (tabstrip()->Contains(hit_view) ||
         hittest == HTCAPTION ||
         hittest == HTTOP) {
-      int scroll_offset = abs(event.y_offset()) > abs(event.x_offset()) ?
-          event.y_offset() : event.x_offset();
+      scroll_remainder_x_ += event.x_offset();
+      scroll_remainder_y_ += event.y_offset();
+
+      // Number of integer scroll events that have passed in each direction.
+      int whole_scroll_amount_x =
+          std::lround(static_cast<double>(scroll_remainder_x_) /
+                      ui::MouseWheelEvent::kWheelDelta);
+      int whole_scroll_amount_y =
+          std::lround(static_cast<double>(scroll_remainder_y_) /
+                      ui::MouseWheelEvent::kWheelDelta);
+
+      // Adjust the remainder such that any whole scrolls we have taken action
+      // for don't count towards the scroll remainder.
+      scroll_remainder_x_ -=
+          whole_scroll_amount_x * ui::MouseWheelEvent::kWheelDelta;
+      scroll_remainder_y_ -=
+          whole_scroll_amount_y * ui::MouseWheelEvent::kWheelDelta;
+
+      // Count a scroll in either axis - summing the axes works for this.
+      int whole_scroll_offset = whole_scroll_amount_x + whole_scroll_amount_y;
+
       Browser* browser = browser_view_->browser();
       TabStripModel* model = browser->tab_strip_model();
       // Switch to the next tab only if not at the end of the tab-strip.
-      if (scroll_offset < 0 && model->active_index() + 1 < model->count()) {
+      if (whole_scroll_offset < 0 &&
+          model->active_index() + 1 < model->count()) {
         chrome::SelectNextTab(browser);
         return true;
       }
 
       // Switch to the previous tab only if not at the beginning of the
       // tab-strip.
-      if (scroll_offset > 0 && model->active_index() > 0) {
+      if (whole_scroll_offset > 0 && model->active_index() > 0) {
         chrome::SelectPreviousTab(browser);
         return true;
       }
@@ -151,6 +175,13 @@
   return RootView::OnMouseWheel(event);
 }
 
+void BrowserRootView::OnMouseExited(const ui::MouseEvent& event) {
+  // Reset such that the tab switch always occurs halfway through a smooth
+  // scroll.
+  scroll_remainder_x_ = 0;
+  scroll_remainder_y_ = 0;
+}
+
 void BrowserRootView::OnEventProcessingStarted(ui::Event* event) {
   if (event->IsGestureEvent()) {
     ui::GestureEvent* gesture_event = event->AsGestureEvent();
diff --git a/chrome/browser/ui/views/frame/browser_root_view.h b/chrome/browser/ui/views/frame/browser_root_view.h
index e2678fd..a8760dc 100644
--- a/chrome/browser/ui/views/frame/browser_root_view.h
+++ b/chrome/browser/ui/views/frame/browser_root_view.h
@@ -40,6 +40,7 @@
   int OnPerformDrop(const ui::DropTargetEvent& event) override;
   const char* GetClassName() const override;
   bool OnMouseWheel(const ui::MouseWheelEvent& event) override;
+  void OnMouseExited(const ui::MouseEvent& event) override;
 
  private:
   // ui::EventProcessor:
@@ -69,6 +70,11 @@
   // to the tab strip.
   bool forwarding_to_tab_strip_;
 
+  // Used to calculate partial offsets in scrolls that occur for a smooth
+  // scroll device.
+  int scroll_remainder_x_;
+  int scroll_remainder_y_;
+
   DISALLOW_COPY_AND_ASSIGN(BrowserRootView);
 };
 
diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc
index cd79518..0fd2333 100644
--- a/ui/base/x/x11_util.cc
+++ b/ui/base/x/x11_util.cc
@@ -374,9 +374,7 @@
   return image;
 }
 
-
-int CoalescePendingMotionEvents(const XEvent* xev,
-                                XEvent* last_event) {
+int CoalescePendingMotionEvents(const XEvent* xev, XEvent* last_event) {
   XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
   int num_coalesced = 0;
   XDisplay* display = xev->xany.display;
@@ -405,7 +403,9 @@
     if (next_event.type == GenericEvent &&
         next_event.xgeneric.evtype == event_type &&
         !ui::DeviceDataManagerX11::GetInstance()->IsCMTGestureEvent(
-            &next_event)) {
+            &next_event) &&
+        ui::DeviceDataManagerX11::GetInstance()->GetScrollClassEventDetail(
+            &next_event) == SCROLL_TYPE_NO_SCROLL) {
       XIDeviceEvent* next_xievent =
           static_cast<XIDeviceEvent*>(next_event.xcookie.data);
       // Confirm that the motion event is targeted at the same window
@@ -414,8 +414,7 @@
           xievent->child == next_xievent->child &&
           xievent->detail == next_xievent->detail &&
           xievent->buttons.mask_len == next_xievent->buttons.mask_len &&
-          (memcmp(xievent->buttons.mask,
-                  next_xievent->buttons.mask,
+          (memcmp(xievent->buttons.mask, next_xievent->buttons.mask,
                   xievent->buttons.mask_len) == 0) &&
           xievent->mods.base == next_xievent->mods.base &&
           xievent->mods.latched == next_xievent->mods.latched &&
diff --git a/ui/events/devices/x11/device_data_manager_x11.cc b/ui/events/devices/x11/device_data_manager_x11.cc
index e9769f5d1..58eded9b 100644
--- a/ui/events/devices/x11/device_data_manager_x11.cc
+++ b/ui/events/devices/x11/device_data_manager_x11.cc
@@ -218,6 +218,7 @@
 void DeviceDataManagerX11::UpdateDeviceList(Display* display) {
   cmt_devices_.reset();
   touchpads_.reset();
+  scrollclass_devices_.reset();
   master_pointers_.clear();
   for (int i = 0; i < kMaxDeviceNum; ++i) {
     valuator_count_[i] = 0;
@@ -225,6 +226,10 @@
     data_type_lookup_[i].clear();
     valuator_min_[i].clear();
     valuator_max_[i].clear();
+    scroll_data_[i].horizontal.number = -1;
+    scroll_data_[i].horizontal.seen = false;
+    scroll_data_[i].vertical.number = -1;
+    scroll_data_[i].vertical.seen = false;
     for (int j = 0; j < kMaxSlotNum; j++)
       last_seen_valuator_[i][j].clear();
   }
@@ -280,21 +285,14 @@
     for (int j = 0; j < kMaxSlotNum; j++)
       last_seen_valuator_[deviceid][j].resize(DT_LAST_ENTRY, 0);
     for (int j = 0; j < info.num_classes; ++j) {
-      if (info.classes[j]->type != XIValuatorClass)
-        continue;
-
-      XIValuatorClassInfo* v =
-          reinterpret_cast<XIValuatorClassInfo*>(info.classes[j]);
-      for (int data_type = 0; data_type < DT_LAST_ENTRY; ++data_type) {
-        if (v->label == atoms[data_type]) {
-          valuator_lookup_[deviceid][data_type] = v->number;
-          data_type_lookup_[deviceid][v->number] = data_type;
-          valuator_min_[deviceid][data_type] = v->min;
-          valuator_max_[deviceid][data_type] = v->max;
-          if (IsCMTDataType(data_type))
-            possible_cmt = true;
-          break;
-        }
+      if (info.classes[j]->type == XIValuatorClass) {
+        if (UpdateValuatorClassDevice(
+                reinterpret_cast<XIValuatorClassInfo*>(info.classes[j]), atoms,
+                deviceid))
+          possible_cmt = true;
+      } else if (info.classes[j]->type == XIScrollClass) {
+        UpdateScrollClassDevice(
+            reinterpret_cast<XIScrollClassInfo*>(info.classes[j]), deviceid);
       }
     }
 
@@ -425,6 +423,42 @@
   return cmt_devices_[xievent->sourceid];
 }
 
+int DeviceDataManagerX11::GetScrollClassEventDetail(
+    const base::NativeEvent& native_event) const {
+  if (native_event->type != GenericEvent)
+    return SCROLL_TYPE_NO_SCROLL;
+
+  XIDeviceEvent* xievent =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  if (xievent->sourceid >= kMaxDeviceNum)
+    return SCROLL_TYPE_NO_SCROLL;
+  if (!scrollclass_devices_[xievent->sourceid])
+    return SCROLL_TYPE_NO_SCROLL;
+  int horizontal_id = scroll_data_[xievent->sourceid].horizontal.number;
+  int vertical_id = scroll_data_[xievent->sourceid].vertical.number;
+  return (XIMaskIsSet(xievent->valuators.mask, horizontal_id)
+              ? SCROLL_TYPE_HORIZONTAL
+              : 0) |
+         (XIMaskIsSet(xievent->valuators.mask, vertical_id)
+              ? SCROLL_TYPE_VERTICAL
+              : 0);
+}
+
+int DeviceDataManagerX11::GetScrollClassDeviceDetail(
+    const base::NativeEvent& native_event) const {
+  XEvent& xev = *native_event;
+  if (xev.type != GenericEvent)
+    return SCROLL_TYPE_NO_SCROLL;
+
+  XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
+  if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
+    return SCROLL_TYPE_NO_SCROLL;
+  const int sourceid = xiev->sourceid;
+  const ScrollInfo& device_data = scroll_data_[sourceid];
+  return (device_data.vertical.number >= 0 ? SCROLL_TYPE_VERTICAL : 0) |
+         (device_data.horizontal.number >= 0 ? SCROLL_TYPE_HORIZONTAL : 0);
+}
+
 bool DeviceDataManagerX11::IsCMTGestureEvent(
     const base::NativeEvent& native_event) const {
   return (IsScrollEvent(native_event) ||
@@ -517,6 +551,49 @@
     *finger_count = static_cast<int>(data[DT_CMT_FINGER_COUNT]);
 }
 
+void DeviceDataManagerX11::GetScrollClassOffsets(
+    const base::NativeEvent& native_event,
+    double* x_offset,
+    double* y_offset) {
+  DCHECK_NE(SCROLL_TYPE_NO_SCROLL, GetScrollClassDeviceDetail(native_event));
+  XEvent& xev = *native_event;
+
+  *x_offset = 0;
+  *y_offset = 0;
+
+  if (xev.type != GenericEvent)
+    return;
+
+  XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
+  if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
+    return;
+  const int sourceid = xiev->sourceid;
+  double* valuators = xiev->valuators.values;
+
+  ScrollInfo* info = &scroll_data_[sourceid];
+
+  const int horizontal_number = info->horizontal.number;
+  const int vertical_number = info->vertical.number;
+
+  for (int i = 0; i <= valuator_count_[sourceid]; ++i) {
+    if (!XIMaskIsSet(xiev->valuators.mask, i))
+      continue;
+    if (i == horizontal_number) {
+      *x_offset = ExtractAndUpdateScrollOffset(&info->horizontal, *valuators);
+    } else if (i == vertical_number) {
+      *y_offset = ExtractAndUpdateScrollOffset(&info->vertical, *valuators);
+    }
+    valuators++;
+  }
+}
+
+void DeviceDataManagerX11::InvalidateScrollClasses() {
+  for (int i = 0; i < kMaxDeviceNum; i++) {
+    scroll_data_[i].horizontal.seen = false;
+    scroll_data_[i].vertical.seen = false;
+  }
+}
+
 void DeviceDataManagerX11::GetFlingData(
     const base::NativeEvent& native_event,
     float* vx,
@@ -697,6 +774,60 @@
   }
 }
 
+bool DeviceDataManagerX11::UpdateValuatorClassDevice(
+    XIValuatorClassInfo* valuator_class_info,
+    Atom* atoms,
+    int deviceid) {
+  DCHECK(deviceid >= 0 && deviceid < kMaxDeviceNum);
+  Atom* label =
+      std::find(atoms, atoms + DT_LAST_ENTRY, valuator_class_info->label);
+  if (label == atoms + DT_LAST_ENTRY) {
+    return false;
+  }
+  int data_type = label - atoms;
+  DCHECK_GE(data_type, 0);
+  DCHECK_LT(data_type, DT_LAST_ENTRY);
+
+  valuator_lookup_[deviceid][data_type] = valuator_class_info->number;
+  data_type_lookup_[deviceid][valuator_class_info->number] = data_type;
+  valuator_min_[deviceid][data_type] = valuator_class_info->min;
+  valuator_max_[deviceid][data_type] = valuator_class_info->max;
+  return IsCMTDataType(data_type);
+}
+
+void DeviceDataManagerX11::UpdateScrollClassDevice(
+    XIScrollClassInfo* scroll_class_info,
+    int deviceid) {
+  DCHECK(deviceid >= 0 && deviceid < kMaxDeviceNum);
+  ScrollInfo& info = scroll_data_[deviceid];
+  switch (scroll_class_info->scroll_type) {
+    case XIScrollTypeVertical:
+      info.vertical.number = scroll_class_info->number;
+      info.vertical.increment = scroll_class_info->increment;
+      info.vertical.position = 0;
+      info.vertical.seen = false;
+      break;
+    case XIScrollTypeHorizontal:
+      info.horizontal.number = scroll_class_info->number;
+      info.horizontal.increment = scroll_class_info->increment;
+      info.horizontal.position = 0;
+      info.horizontal.seen = false;
+      break;
+  }
+  scrollclass_devices_[deviceid] = true;
+}
+
+double DeviceDataManagerX11::ExtractAndUpdateScrollOffset(
+    ScrollInfo::AxisInfo* axis,
+    double valuator) const {
+  double offset = 0;
+  if (axis->seen)
+    offset = axis->position - valuator;
+  axis->seen = true;
+  axis->position = valuator;
+  return offset / axis->increment;
+}
+
 bool DeviceDataManagerX11::TouchEventNeedsCalibrate(int touch_device_id) const {
 #if defined(OS_CHROMEOS)
   if (!base::SysInfo::IsRunningOnChromeOS())
diff --git a/ui/events/devices/x11/device_data_manager_x11.h b/ui/events/devices/x11/device_data_manager_x11.h
index 33986e9..5991e89 100644
--- a/ui/events/devices/x11/device_data_manager_x11.h
+++ b/ui/events/devices/x11/device_data_manager_x11.h
@@ -40,6 +40,13 @@
   kGestureMetricsTypeUnknown,
 };
 
+// A bitfield describing which scroll axes are enabled for a device.
+enum ScrollType {
+  SCROLL_TYPE_NO_SCROLL = 0,
+  SCROLL_TYPE_HORIZONTAL = 1 << 0,
+  SCROLL_TYPE_VERTICAL = 1 << 1,
+};
+
 // A class that extracts and tracks the input events data. It currently handles
 // mouse, touchpad and touchscreen devices.
 class EVENTS_DEVICES_EXPORT DeviceDataManagerX11 : public DeviceDataManager {
@@ -152,6 +159,16 @@
   // is a CMT event (e.g. it could be a mouse pointer move).
   bool IsCMTDeviceEvent(const base::NativeEvent& native_event) const;
 
+  // Check if the event contains information about a ScrollClass, and
+  // report which scroll axes are contained in this event, defined by
+  // ScrollType.
+  int GetScrollClassEventDetail(const base::NativeEvent& native_event) const;
+
+  // Check if the event comes from a device that has a ScrollClass, and
+  // report which scroll axes it supports as a bit field, defined by
+  // ScrollType.
+  int GetScrollClassDeviceDetail(const base::NativeEvent& native_event) const;
+
   // Check if the event is one of the CMT gesture events (scroll, fling,
   // metrics etc.).
   bool IsCMTGestureEvent(const base::NativeEvent& native_event) const;
@@ -174,6 +191,17 @@
                         float* y_offset_ordinal,
                         int* finger_count);
 
+  // Extract data from a scroll class event (smooth scrolling). User must
+  // first verify the event type with GetScrollClassEventDetail.
+  // Pointers shouldn't be NULL.
+  void GetScrollClassOffsets(const base::NativeEvent& native_event,
+                             double* x_offset,
+                             double* y_offset);
+
+  // Invalidate stored scroll class counters, since they can change when
+  // pointing at other windows.
+  void InvalidateScrollClasses();
+
   // Extract data from a fling event. User must first verify the event type
   // with IsFlingEvent. Pointers shouldn't be NULL.
   void GetFlingData(const base::NativeEvent& native_event,
@@ -255,6 +283,23 @@
       const std::vector<KeyboardDevice>& devices) override;
 
  private:
+  // Information about scroll valuators
+  struct ScrollInfo {
+    struct AxisInfo {
+      // The scroll valuator number of this scroll axis.
+      int number;
+      // The scroll increment; a value of n indicates n movement equals one
+      // traditional scroll unit.
+      double increment;
+      // Current scroll position; used to find the difference between events.
+      double position;
+      // If true then scroll has been seen in this direction.
+      bool seen;
+    };
+
+    AxisInfo vertical, horizontal;
+  };
+
   DeviceDataManagerX11();
   ~DeviceDataManagerX11() override;
 
@@ -267,6 +312,25 @@
                                   double min_value,
                                   double max_value);
 
+  // Updates a device based on a Valuator class info. Returns true if the
+  // device is a possible CMT device.
+  bool UpdateValuatorClassDevice(XIValuatorClassInfo* valuator_class_info,
+                                 Atom* atoms,
+                                 int deviceid);
+
+  // Updates a device based on a Scroll class info.
+  void UpdateScrollClassDevice(XIScrollClassInfo* scroll_class_info,
+                               int deviceid);
+
+  // Normalize the scroll amount according to the increment size.
+  // *value /= increment
+  // *value is expected to be 1 or -1.
+  // Returns true and sets the normalized value in |value| if normalization is
+  // successful. Returns false and |value| is unchanged otherwise.
+  double ExtractAndUpdateScrollOffset(
+      DeviceDataManagerX11::ScrollInfo::AxisInfo* axis,
+      double valuator) const;
+
   static const int kMaxXIEventType = XI_LASTEVENT + 1;
   static const int kMaxSlotNum = 10;
 
@@ -280,6 +344,7 @@
   // should be processed.
   std::bitset<kMaxDeviceNum> cmt_devices_;
   std::bitset<kMaxDeviceNum> touchpads_;
+  std::bitset<kMaxDeviceNum> scrollclass_devices_;
 
   // List of the master pointer devices.
   std::vector<int> master_pointers_;
@@ -298,6 +363,10 @@
   // by valuator_lookup_[device_id][data_type].
   std::vector<int> valuator_lookup_[kMaxDeviceNum];
 
+  // Index table to find the horizontal and vertical scroll valuator
+  // numbers, scroll increments and scroll position.
+  ScrollInfo scroll_data_[kMaxDeviceNum];
+
   // Index table to find the DataType for valuator on the specific device
   // by data_type_lookup_[device_id][valuator].
   std::vector<int> data_type_lookup_[kMaxDeviceNum];
diff --git a/ui/events/devices/x11/touch_factory_x11.cc b/ui/events/devices/x11/touch_factory_x11.cc
index 054a982..b03b480 100644
--- a/ui/events/devices/x11/touch_factory_x11.cc
+++ b/ui/events/devices/x11/touch_factory_x11.cc
@@ -221,6 +221,10 @@
   XISetMask(mask, XI_ButtonPress);
   XISetMask(mask, XI_ButtonRelease);
   XISetMask(mask, XI_Motion);
+  // HierarchyChanged and DeviceChanged allow X11EventSource to still pick up
+  // these events.
+  XISetMask(mask, XI_HierarchyChanged);
+  XISetMask(mask, XI_DeviceChanged);
 #if defined(OS_CHROMEOS)
   // XGrabKey() must be replaced with XI2 keyboard grab if XI2 key events are
   // enabled on desktop Linux.
diff --git a/ui/events/platform/x11/x11_event_source.cc b/ui/events/platform/x11/x11_event_source.cc
index e642949..ba55e0b 100644
--- a/ui/events/platform/x11/x11_event_source.cc
+++ b/ui/events/platform/x11/x11_event_source.cc
@@ -135,10 +135,18 @@
 uint32_t X11EventSource::DispatchEvent(XEvent* xevent) {
   uint32_t action = PlatformEventSource::DispatchEvent(xevent);
   if (xevent->type == GenericEvent &&
-      xevent->xgeneric.evtype == XI_HierarchyChanged) {
+      (xevent->xgeneric.evtype == XI_HierarchyChanged ||
+       xevent->xgeneric.evtype == XI_DeviceChanged)) {
     ui::UpdateDeviceList();
     hotplug_event_handler_->OnHotplugEvent();
   }
+
+  if (xevent->type == EnterNotify &&
+      xevent->xcrossing.detail != NotifyInferior &&
+      xevent->xcrossing.mode != NotifyUngrab) {
+    // Clear stored scroll data
+    ui::DeviceDataManagerX11::GetInstance()->InvalidateScrollClasses();
+  }
   return action;
 }
 
diff --git a/ui/events/x/events_x.cc b/ui/events/x/events_x.cc
index 9d85351..5a23b08 100644
--- a/ui/events/x/events_x.cc
+++ b/ui/events/x/events_x.cc
@@ -444,6 +444,9 @@
             return devices->IsTouchpadXInputEvent(native_event) ? ET_SCROLL
                                                                 : ET_MOUSEWHEEL;
           }
+          if (devices->GetScrollClassEventDetail(native_event) !=
+              SCROLL_TYPE_NO_SCROLL)
+            return ET_MOUSEWHEEL;
           if (devices->IsCMTMetricsEvent(native_event))
             return ET_UMA_DATA;
           if (GetButtonMaskForX2Event(xievent))
@@ -787,30 +790,38 @@
                       float* x_offset_ordinal,
                       float* y_offset_ordinal,
                       int* finger_count) {
-  if (!DeviceDataManagerX11::GetInstance()->IsScrollEvent(native_event))
-    return false;
+  if (DeviceDataManagerX11::GetInstance()->IsScrollEvent(native_event)) {
+    // Temp values to prevent passing NULLs to DeviceDataManager.
+    float x_offset_, y_offset_;
+    float x_offset_ordinal_, y_offset_ordinal_;
+    int finger_count_;
+    if (!x_offset)
+      x_offset = &x_offset_;
+    if (!y_offset)
+      y_offset = &y_offset_;
+    if (!x_offset_ordinal)
+      x_offset_ordinal = &x_offset_ordinal_;
+    if (!y_offset_ordinal)
+      y_offset_ordinal = &y_offset_ordinal_;
+    if (!finger_count)
+      finger_count = &finger_count_;
 
-  // Temp values to prevent passing NULLs to DeviceDataManager.
-  float x_offset_, y_offset_;
-  float x_offset_ordinal_, y_offset_ordinal_;
-  int finger_count_;
-  if (!x_offset)
-    x_offset = &x_offset_;
-  if (!y_offset)
-    y_offset = &y_offset_;
-  if (!x_offset_ordinal)
-    x_offset_ordinal = &x_offset_ordinal_;
-  if (!y_offset_ordinal)
-    y_offset_ordinal = &y_offset_ordinal_;
-  if (!finger_count)
-    finger_count = &finger_count_;
+    DeviceDataManagerX11::GetInstance()->GetScrollOffsets(
+        native_event, x_offset, y_offset, x_offset_ordinal, y_offset_ordinal,
+        finger_count);
+    return true;
+  }
 
-  DeviceDataManagerX11::GetInstance()->GetScrollOffsets(
-      native_event,
-      x_offset, y_offset,
-      x_offset_ordinal, y_offset_ordinal,
-      finger_count);
-  return true;
+  if (DeviceDataManagerX11::GetInstance()->GetScrollClassDeviceDetail(
+          native_event) != SCROLL_TYPE_NO_SCROLL) {
+    double x_scroll_offset, y_scroll_offset;
+    DeviceDataManagerX11::GetInstance()->GetScrollClassOffsets(
+        native_event, &x_scroll_offset, &y_scroll_offset);
+    *x_offset = x_scroll_offset * kWheelScrollAmount;
+    *y_offset = y_scroll_offset * kWheelScrollAmount;
+    return true;
+  }
+  return false;
 }
 
 bool GetFlingData(const base::NativeEvent& native_event,