Clone of chromium aad1ce808763f59c7a3753e08f1500a104ecc6fd refs/remotes/origin/HEAD
diff --git a/ui/events/BUILD.gn b/ui/events/BUILD.gn
new file mode 100644
index 0000000..7277cb3
--- /dev/null
+++ b/ui/events/BUILD.gn
@@ -0,0 +1,330 @@
+# Copyright 2014 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.
+
+import("//build/config/ui.gni")
+
+static_library("dom4_keycode_converter") {
+  sources = [
+    "keycodes/dom4/keycode_converter.cc",
+    "keycodes/dom4/keycode_converter.h",
+    "keycodes/dom4/keycode_converter_data.h",
+  ]
+
+  deps = [ "//base" ]
+}
+
+component("events_base") {
+  sources = [
+    "device_data_manager.cc",
+    "device_data_manager.h",
+    "device_hotplug_event_observer.h",
+    "event_constants.h",
+    "event_switches.cc",
+    "event_switches.h",
+    "events_base_export.h",
+    "gesture_event_details.cc",
+    "gesture_event_details.h",
+    "gestures/fling_curve.cc",
+    "gestures/fling_curve.h",
+    "gestures/gesture_configuration.cc",
+    "gestures/gesture_configuration.h",
+    "keycodes/keyboard_code_conversion.cc",
+    "keycodes/keyboard_code_conversion.h",
+    "keycodes/keyboard_code_conversion_android.cc",
+    "keycodes/keyboard_code_conversion_android.h",
+    "keycodes/keyboard_code_conversion_mac.h",
+    "keycodes/keyboard_code_conversion_mac.mm",
+    "keycodes/keyboard_code_conversion_win.cc",
+    "keycodes/keyboard_code_conversion_win.h",
+    "keycodes/keyboard_codes.h",
+    "latency_info.cc",
+    "latency_info.h",
+    "touchscreen_device.cc",
+    "touchscreen_device.h",
+  ]
+
+  defines = [ "EVENTS_BASE_IMPLEMENTATION" ]
+
+  deps = [
+    ":dom4_keycode_converter",
+    "//base/third_party/dynamic_annotations",
+    "//skia",
+  ]
+
+  public_deps = [
+    "//base",
+    "//ui/events/platform",
+    "//ui/gfx",
+    "//ui/gfx/geometry",
+  ]
+
+  if (use_x11) {
+    configs += [ "//build/config/linux:x11" ]
+
+    sources += [
+      "keycodes/keyboard_code_conversion_x.cc",
+      "keycodes/keyboard_code_conversion_x.h",
+      "x/device_data_manager_x11.cc",
+      "x/device_data_manager_x11.h",
+      "x/device_list_cache_x.cc",
+      "x/device_list_cache_x.h",
+      "x/hotplug_event_handler_x11.cc",
+      "x/hotplug_event_handler_x11.h",
+      "x/keysym_to_unicode.cc",
+      "x/keysym_to_unicode.h",
+      "x/touch_factory_x11.cc",
+      "x/touch_factory_x11.h",
+    ]
+
+    deps += [ "//ui/gfx/x" ]
+  }
+}
+
+component("events") {
+  sources = [
+    "cocoa/cocoa_event_utils.h",
+    "cocoa/cocoa_event_utils.mm",
+    "cocoa/events_mac.mm",
+    "event.cc",
+    "event.h",
+    "event_dispatcher.cc",
+    "event_dispatcher.h",
+    "event_handler.cc",
+    "event_handler.h",
+    "event_processor.cc",
+    "event_processor.h",
+    "event_rewriter.h",
+    "event_source.cc",
+    "event_source.h",
+    "event_target.cc",
+    "event_target.h",
+    "event_target_iterator.h",
+    "event_targeter.cc",
+    "event_targeter.h",
+    "event_utils.cc",
+    "event_utils.h",
+    "events_export.h",
+    "events_stub.cc",
+    "gestures/gesture_recognizer_impl_mac.cc",
+    "gestures/gesture_types.h",
+    "win/events_win.cc",
+  ]
+
+  defines = [ "EVENTS_IMPLEMENTATION" ]
+
+  public_deps = [
+    ":events_base",
+  ]
+  deps = [
+    ":dom4_keycode_converter",
+    ":gesture_detection",
+    "//base/third_party/dynamic_annotations",
+    "//skia",
+    "//ui/gfx",
+    "//ui/gfx/geometry",
+  ]
+
+  if (use_x11) {
+    sources += [ "x/events_x.cc" ]
+    configs += [
+      "//build/config/linux:glib",
+      "//build/config/linux:x11",
+    ]
+    deps += [ "//ui/gfx/x" ]
+  }
+
+  if (!is_chromeos && is_linux) {
+    sources += [
+      "linux/text_edit_command_auralinux.cc",
+      "linux/text_edit_command_auralinux.h",
+      "linux/text_edit_key_bindings_delegate_auralinux.cc",
+      "linux/text_edit_key_bindings_delegate_auralinux.h",
+    ]
+  }
+
+  if (use_ozone) {
+    sources += [
+      "ozone/events_ozone.cc",
+    ]
+  }
+
+  if (use_aura) {
+    sources += [
+      "gestures/gesture_provider_aura.cc",
+      "gestures/gesture_provider_aura.h",
+      "gestures/motion_event_aura.cc",
+      "gestures/motion_event_aura.h",
+      "gestures/gesture_recognizer.h",
+      "gestures/gesture_recognizer_impl.cc",
+      "gestures/gesture_recognizer_impl.h",
+    ]
+  }
+
+  if (is_win || is_mac || use_x11 || use_ozone) {
+    sources -= [ "events_stub.cc" ]
+  }
+}
+
+component("gesture_detection") {
+  sources = [
+    "gesture_detection/bitset_32.h",
+    "gesture_detection/filtered_gesture_provider.cc",
+    "gesture_detection/filtered_gesture_provider.h",
+    "gesture_detection/gesture_config_helper.h",
+    "gesture_detection/gesture_detection_export.h",
+    "gesture_detection/gesture_detector.cc",
+    "gesture_detection/gesture_detector.h",
+    "gesture_detection/gesture_event_data.cc",
+    "gesture_detection/gesture_event_data.h",
+    "gesture_detection/gesture_event_data_packet.cc",
+    "gesture_detection/gesture_event_data_packet.h",
+    "gesture_detection/gesture_listeners.cc",
+    "gesture_detection/gesture_listeners.h",
+    "gesture_detection/gesture_provider.cc",
+    "gesture_detection/gesture_provider.h",
+    "gesture_detection/gesture_touch_uma_histogram.cc",
+    "gesture_detection/gesture_touch_uma_histogram.h",
+    "gesture_detection/motion_event.cc",
+    "gesture_detection/motion_event.h",
+    "gesture_detection/motion_event_buffer.cc",
+    "gesture_detection/motion_event_buffer.h",
+    "gesture_detection/motion_event_generic.cc",
+    "gesture_detection/motion_event_generic.h",
+    "gesture_detection/scale_gesture_detector.cc",
+    "gesture_detection/scale_gesture_detector.h",
+    "gesture_detection/scale_gesture_listeners.cc",
+    "gesture_detection/scale_gesture_listeners.h",
+    "gesture_detection/snap_scroll_controller.cc",
+    "gesture_detection/snap_scroll_controller.h",
+    "gesture_detection/touch_disposition_gesture_filter.cc",
+    "gesture_detection/touch_disposition_gesture_filter.h",
+    "gesture_detection/velocity_tracker_state.cc",
+    "gesture_detection/velocity_tracker_state.h",
+    "gesture_detection/velocity_tracker.cc",
+    "gesture_detection/velocity_tracker.h",
+  ]
+
+  deps = [
+    ":events_base",
+    "//base",
+    "//ui/gfx",
+    "//ui/gfx/geometry",
+  ]
+
+  defines = [ "GESTURE_DETECTION_IMPLEMENTATION" ]
+
+  if (is_android) {
+    sources += [ "gesture_detection/gesture_config_helper_android.cc" ]
+  } else if (use_aura) {
+    sources += [ "gesture_detection/gesture_config_helper_aura.cc" ]
+  } else {
+    sources += [ "gesture_detection/gesture_config_helper.cc" ]
+  }
+}
+
+source_set("test_support") {
+  sources = [
+    "test/cocoa_test_event_utils.h",
+    "test/cocoa_test_event_utils.mm",
+    "test/event_generator.cc",
+    "test/event_generator.h",
+    "test/events_test_utils.cc",
+    "test/events_test_utils.h",
+    "test/mock_motion_event.cc",
+    "test/mock_motion_event.h",
+    "test/platform_event_waiter.cc",
+    "test/platform_event_waiter.h",
+    "test/test_event_handler.cc",
+    "test/test_event_handler.h",
+    "test/test_event_processor.cc",
+    "test/test_event_processor.h",
+    "test/test_event_target.cc",
+    "test/test_event_target.h",
+  ]
+
+  public_deps = [
+    ":events",
+    ":events_base",
+    ":gesture_detection",
+  ]
+  deps = [
+    "//base",
+    "//skia",
+    "//ui/events/platform",
+    "//ui/gfx/geometry",
+  ]
+
+  if (is_ios) {
+    sources -= [
+      "test/cocoa_test_event_utils.h",
+      "test/cocoa_test_event_utils.mm",
+    ]
+  }
+
+  if (use_x11) {
+    sources += [
+      "test/events_test_utils_x11.cc",
+      "test/events_test_utils_x11.h",
+    ]
+    deps += [ "//ui/gfx/x" ]
+  }
+}
+
+test("events_unittests") {
+  sources = [
+    "cocoa/events_mac_unittest.mm",
+    "event_dispatcher_unittest.cc",
+    "event_processor_unittest.cc",
+    "event_rewriter_unittest.cc",
+    "event_unittest.cc",
+    "gesture_detection/bitset_32_unittest.cc",
+    "gesture_detection/gesture_event_data_packet_unittest.cc",
+    "gesture_detection/gesture_provider_unittest.cc",
+    "gesture_detection/motion_event_buffer_unittest.cc",
+    "gesture_detection/motion_event_generic_unittest.cc",
+    "gesture_detection/touch_disposition_gesture_filter_unittest.cc",
+    "gesture_detection/velocity_tracker_unittest.cc",
+    "gestures/fling_curve_unittest.cc",
+    "keycodes/dom4/keycode_converter_unittest.cc",
+    "latency_info_unittest.cc",
+    "platform/platform_event_source_unittest.cc",
+    "x/events_x_unittest.cc",
+  ]
+
+  deps = [
+    ":dom4_keycode_converter",
+    ":events",
+    ":events_base",
+    ":gesture_detection",
+    ":test_support",
+    "//base",
+    "//base/test:run_all_unittests",
+    "//skia",
+    "//testing/gtest",
+    "//ui/events/platform",
+    "//ui/gfx:test_support",
+  ]
+
+  if (use_x11) {
+    configs += [ "//build/config/linux:x11" ]
+    deps += [ "//ui/gfx/x" ]
+  } else {
+    sources -= [
+      "x/events_x_unittest.cc",
+    ]
+  }
+
+  if (use_ozone) {
+    sources += [
+      "ozone/evdev/key_event_converter_evdev_unittest.cc",
+      "ozone/evdev/touch_event_converter_evdev_unittest.cc",
+    ]
+  }
+
+  if (use_aura) {
+    sources += [
+      "gestures/gesture_provider_aura_unittest.cc",
+    ]
+  }
+}
diff --git a/ui/events/DEPS b/ui/events/DEPS
new file mode 100644
index 0000000..b273ae3
--- /dev/null
+++ b/ui/events/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+ui/gfx",
+]
diff --git a/ui/events/OWNERS b/ui/events/OWNERS
new file mode 100644
index 0000000..aeec937
--- /dev/null
+++ b/ui/events/OWNERS
@@ -0,0 +1,6 @@
+sadrul@chromium.org
+per-file latency_info*=jbauman@chromium.org
+per-file latency_info*=miletus@chromium.org
+
+# If you're doing structural changes get a review from one of the OWNERS.
+per-file *.gyp*=*
diff --git a/ui/events/PRESUBMIT.py b/ui/events/PRESUBMIT.py
new file mode 100644
index 0000000..bc905a3
--- /dev/null
+++ b/ui/events/PRESUBMIT.py
@@ -0,0 +1,28 @@
+# Copyright 2014 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.
+
+"""Chromium presubmit script for src/ui/events
+
+See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
+for more details on the presubmit API built into gcl.
+"""
+
+def GetPreferredTryMasters(project, change):
+  tests = set(['ash_unittests',
+               'aura_unittests',
+               'events_unittests',
+               'keyboard_unittests',
+               'views_unittests'])
+
+  return {
+    'tryserver.chromium.linux': {
+      'linux_chromium_rel_swarming': tests,
+      'linux_chromium_chromeos_rel_swarming': tests,
+      'linux_chromeos_asan': tests,
+    },
+    'tryserver.chromium.win': {
+      'win_chromium_compile_dbg': tests,
+      'win_chromium_rel_swarming': tests,
+    }
+  }
diff --git a/ui/events/cocoa/cocoa_event_utils.h b/ui/events/cocoa/cocoa_event_utils.h
new file mode 100644
index 0000000..a598435
--- /dev/null
+++ b/ui/events/cocoa/cocoa_event_utils.h
@@ -0,0 +1,26 @@
+// Copyright 2014 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 UI_EVENTS_COCOA_COCOA_EVENT_UTILS_H_
+#define UI_EVENTS_COCOA_COCOA_EVENT_UTILS_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+// Converts the Cocoa |modifiers| bitsum into a ui::EventFlags bitsum.
+EVENTS_EXPORT int EventFlagsFromModifiers(NSUInteger modifiers);
+
+// Retrieves a bitsum of ui::EventFlags represented by |event|,
+// but instead use the modifier flags given by |modifiers|,
+// which is the same format as |-NSEvent modifierFlags|. This allows
+// substitution of the modifiers without having to create a new event from
+// scratch.
+EVENTS_EXPORT int EventFlagsFromNSEventWithModifiers(NSEvent* event,
+                                                     NSUInteger modifiers);
+}  // namespace ui
+
+#endif  // UI_EVENTS_COCOA_COCOA_EVENT_UTILS_H_
diff --git a/ui/events/cocoa/cocoa_event_utils.mm b/ui/events/cocoa/cocoa_event_utils.mm
new file mode 100644
index 0000000..ade083c
--- /dev/null
+++ b/ui/events/cocoa/cocoa_event_utils.mm
@@ -0,0 +1,55 @@
+// Copyright 2014 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.
+
+#import "ui/events/cocoa/cocoa_event_utils.h"
+
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+
+namespace {
+
+bool IsLeftButtonEvent(NSEvent* event) {
+  NSEventType type = [event type];
+  return type == NSLeftMouseDown || type == NSLeftMouseDragged ||
+         type == NSLeftMouseUp;
+}
+
+bool IsRightButtonEvent(NSEvent* event) {
+  NSEventType type = [event type];
+  return type == NSRightMouseDown || type == NSRightMouseDragged ||
+         type == NSRightMouseUp;
+}
+
+bool IsMiddleButtonEvent(NSEvent* event) {
+  if ([event buttonNumber] != 2)
+    return false;
+
+  NSEventType type = [event type];
+  return type == NSOtherMouseDown || type == NSOtherMouseDragged ||
+         type == NSOtherMouseUp;
+}
+
+}  // namespace
+
+namespace ui {
+
+int EventFlagsFromModifiers(NSUInteger modifiers) {
+  int flags = 0;
+  flags |= (modifiers & NSAlphaShiftKeyMask) ? ui::EF_CAPS_LOCK_DOWN : 0;
+  flags |= (modifiers & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0;
+  flags |= (modifiers & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0;
+  flags |= (modifiers & NSAlternateKeyMask) ? ui::EF_ALT_DOWN : 0;
+  flags |= (modifiers & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0;
+  return flags;
+}
+
+int EventFlagsFromNSEventWithModifiers(NSEvent* event, NSUInteger modifiers) {
+  int flags = EventFlagsFromModifiers(modifiers);
+  flags |= IsLeftButtonEvent(event) ? ui::EF_LEFT_MOUSE_BUTTON : 0;
+  flags |= IsRightButtonEvent(event) ? ui::EF_RIGHT_MOUSE_BUTTON : 0;
+  flags |= IsMiddleButtonEvent(event) ? ui::EF_MIDDLE_MOUSE_BUTTON : 0;
+  return flags;
+}
+
+}  // namespace ui
diff --git a/ui/events/cocoa/events_mac.mm b/ui/events/cocoa/events_mac.mm
new file mode 100644
index 0000000..4772b27
--- /dev/null
+++ b/ui/events/cocoa/events_mac.mm
@@ -0,0 +1,274 @@
+// Copyright 2014 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 "ui/events/event_utils.h"
+
+#include <Cocoa/Cocoa.h>
+
+#import "base/mac/mac_util.h"
+#import "base/mac/sdk_forward_declarations.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "ui/events/cocoa/cocoa_event_utils.h"
+#include "ui/events/event_utils.h"
+#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/vector2d.h"
+
+namespace ui {
+
+void UpdateDeviceList() {
+  NOTIMPLEMENTED();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+  NSEventType type = [native_event type];
+  switch (type) {
+    case NSKeyDown:
+      return ET_KEY_PRESSED;
+    case NSKeyUp:
+      return ET_KEY_RELEASED;
+    case NSLeftMouseDown:
+    case NSRightMouseDown:
+    case NSOtherMouseDown:
+      return ET_MOUSE_PRESSED;
+    case NSLeftMouseUp:
+    case NSRightMouseUp:
+    case NSOtherMouseUp:
+      return ET_MOUSE_RELEASED;
+    case NSLeftMouseDragged:
+    case NSRightMouseDragged:
+    case NSOtherMouseDragged:
+      return ET_MOUSE_DRAGGED;
+    case NSMouseMoved:
+      return ET_MOUSE_MOVED;
+    case NSScrollWheel:
+      return ET_MOUSEWHEEL;
+    case NSMouseEntered:
+      return ET_MOUSE_ENTERED;
+    case NSMouseExited:
+      return ET_MOUSE_EXITED;
+    case NSEventTypeSwipe:
+      return ET_SCROLL_FLING_START;
+    case NSFlagsChanged:
+    case NSAppKitDefined:
+    case NSSystemDefined:
+    case NSApplicationDefined:
+    case NSPeriodic:
+    case NSCursorUpdate:
+    case NSTabletPoint:
+    case NSTabletProximity:
+    case NSEventTypeGesture:
+    case NSEventTypeMagnify:
+    case NSEventTypeRotate:
+    case NSEventTypeBeginGesture:
+    case NSEventTypeEndGesture:
+      NOTIMPLEMENTED() << type;
+      break;
+    default:
+      NOTIMPLEMENTED() << type;
+      break;
+  }
+  return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& event) {
+  NSUInteger modifiers = [event modifierFlags];
+  return EventFlagsFromNSEventWithModifiers(event, modifiers);
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+  NSTimeInterval since_system_startup = [native_event timestamp];
+  // Truncate to extract seconds before doing floating point arithmetic.
+  int64_t seconds = since_system_startup;
+  since_system_startup -= seconds;
+  int64_t microseconds = since_system_startup * 1000000;
+  return base::TimeDelta::FromSeconds(seconds) +
+      base::TimeDelta::FromMicroseconds(microseconds);
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+  NSWindow* window = [native_event window];
+  if (!window) {
+    NOTIMPLEMENTED();  // Point will be in screen coordinates.
+    return gfx::Point();
+  }
+  NSPoint location = [native_event locationInWindow];
+  NSRect content_rect = [window contentRectForFrameRect:[window frame]];
+  return gfx::Point(location.x, NSHeight(content_rect) - location.y);
+}
+
+gfx::Point EventSystemLocationFromNative(
+    const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return gfx::Point();
+}
+
+int EventButtonFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+    const base::NativeEvent& native_event) {
+  NSEventType type = [native_event type];
+  switch (type) {
+    case NSLeftMouseDown:
+    case NSLeftMouseUp:
+    case NSLeftMouseDragged:
+      return EF_LEFT_MOUSE_BUTTON;
+    case NSRightMouseDown:
+    case NSRightMouseUp:
+    case NSRightMouseDragged:
+      return EF_RIGHT_MOUSE_BUTTON;
+    case NSOtherMouseDown:
+    case NSOtherMouseUp:
+    case NSOtherMouseDragged:
+      return EF_MIDDLE_MOUSE_BUTTON;
+  }
+  return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& event) {
+  if ([event respondsToSelector:@selector(hasPreciseScrollingDeltas)] &&
+      [event hasPreciseScrollingDeltas]) {
+    // Handle continuous scrolling devices such as a Magic Mouse or a trackpad.
+    // -scrollingDelta{X|Y} have float return types but they return values that
+    // are already rounded to integers.
+    // The values are the same as the values returned from calling
+    // CGEventGetIntegerValueField(kCGScrollWheelEventPointDeltaAxis{1|2}).
+    return gfx::Vector2d([event scrollingDeltaX], [event scrollingDeltaY]);
+  } else {
+    // Empirically, a value of 0.1 is typical for one mousewheel click. Positive
+    // values when scrolling up or to the left. Scrolling quickly results in a
+    // higher delta per click, up to about 15.0. (Quartz documentation suggests
+    // +/-10).
+    // Multiply by 1000 to vaguely approximate WHEEL_DELTA on Windows (120).
+    const CGFloat kWheelDeltaMultiplier = 1000;
+    return gfx::Vector2d(kWheelDeltaMultiplier * [event deltaX],
+                         kWheelDeltaMultiplier * [event deltaY]);
+  }
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+  return [event copy];
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+  [event release];
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+}
+
+int GetTouchId(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+                      float* x_offset,
+                      float* y_offset,
+                      float* x_offset_ordinal,
+                      float* y_offset_ordinal,
+                      int* finger_count) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+                  float* vx,
+                  float* vy,
+                  float* vx_ordinal,
+                  float* vy_ordinal,
+                  bool* is_cancel) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+  return KeyboardCodeFromNSEvent(native_event);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+  return CodeFromNSEvent(native_event);
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+  return native_event.keyCode;
+}
+
+uint32 WindowsKeycodeFromNative(const base::NativeEvent& native_event) {
+  return static_cast<uint32>(KeyboardCodeFromNSEvent(native_event));
+}
+
+uint16 TextFromNative(const base::NativeEvent& native_event) {
+  NSString* text = @"";
+  if ([native_event type] != NSFlagsChanged)
+    text = [native_event characters];
+
+  // These exceptions are based on WebInputEventFactoryMac.mm:
+  uint32 windows_keycode = WindowsKeycodeFromNative(native_event);
+  if (windows_keycode == '\r')
+    text = @"\r";
+  if ([text isEqualToString:@"\x7F"])
+    text = @"\x8";
+  if (windows_keycode == 9)
+    text = @"\x9";
+
+  uint16 return_value;
+  [text getCharacters:&return_value];
+  return return_value;
+}
+
+uint16 UnmodifiedTextFromNative(const base::NativeEvent& native_event) {
+  NSString* text = @"";
+  if ([native_event type] != NSFlagsChanged)
+    text = [native_event charactersIgnoringModifiers];
+
+  // These exceptions are based on WebInputEventFactoryMac.mm:
+  uint32 windows_keycode = WindowsKeycodeFromNative(native_event);
+  if (windows_keycode == '\r')
+    text = @"\r";
+  if ([text isEqualToString:@"\x7F"])
+    text = @"\x8";
+  if (windows_keycode == 9)
+    text = @"\x9";
+
+  uint16 return_value;
+  [text getCharacters:&return_value];
+  return return_value;
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+  return false;
+}
+
+}  // namespace ui
diff --git a/ui/events/cocoa/events_mac_unittest.mm b/ui/events/cocoa/events_mac_unittest.mm
new file mode 100644
index 0000000..f58cfd0
--- /dev/null
+++ b/ui/events/cocoa/events_mac_unittest.mm
@@ -0,0 +1,314 @@
+// Copyright 2014 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 "ui/events/event_utils.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/scoped_cftyperef.h"
+#import "base/mac/scoped_objc_class_swizzler.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_constants.h"
+#import "ui/events/test/cocoa_test_event_utils.h"
+#include "ui/gfx/point.h"
+#import "ui/gfx/test/ui_cocoa_test_helper.h"
+
+namespace {
+
+NSWindow* g_test_window = nil;
+
+}  // namespace
+
+// Mac APIs for creating test events are frustrating. Quartz APIs have, e.g.,
+// CGEventCreateMouseEvent() which can't set a window or modifier flags.
+// Cocoa APIs have +[NSEvent mouseEventWithType:..] which can't set
+// buttonNumber or scroll deltas. To work around this, these tests use some
+// Objective C magic to donate member functions to NSEvent temporarily.
+@interface MiddleMouseButtonNumberDonor : NSObject
+@end
+
+@interface TestWindowDonor : NSObject
+@end
+
+@implementation MiddleMouseButtonNumberDonor
+- (NSInteger)buttonNumber { return 2; }
+@end
+
+@implementation TestWindowDonor
+- (NSWindow*)window { return g_test_window; }
+@end
+
+namespace ui {
+
+namespace {
+
+class EventsMacTest : public CocoaTest {
+ public:
+  EventsMacTest() {}
+
+  gfx::Point Flip(gfx::Point window_location) {
+    NSRect window_frame = [test_window() frame];
+    CGFloat content_height =
+        NSHeight([test_window() contentRectForFrameRect:window_frame]);
+    window_location.set_y(content_height - window_location.y());
+    return window_location;
+  }
+
+  void SwizzleMiddleMouseButton() {
+    DCHECK(!swizzler_);
+    swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
+        [NSEvent class],
+        [MiddleMouseButtonNumberDonor class],
+        @selector(buttonNumber)));
+  }
+
+  void SwizzleTestWindow() {
+    DCHECK(!g_test_window);
+    DCHECK(!swizzler_);
+    g_test_window = test_window();
+    swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
+        [NSEvent class], [TestWindowDonor class], @selector(window)));
+  }
+
+  void ClearSwizzle() {
+    swizzler_.reset();
+    g_test_window = nil;
+  }
+
+  NSEvent* TestMouseEvent(NSEventType type,
+                          const gfx::Point &window_location,
+                          NSInteger modifier_flags) {
+    NSPoint point = NSPointFromCGPoint(Flip(window_location).ToCGPoint());
+    return [NSEvent mouseEventWithType:type
+                              location:point
+                         modifierFlags:modifier_flags
+                             timestamp:0
+                          windowNumber:[test_window() windowNumber]
+                               context:nil
+                           eventNumber:0
+                            clickCount:0
+                              pressure:1.0];
+  }
+
+  NSEvent* TestScrollEvent(const gfx::Point& window_location,
+                           int32_t delta_x,
+                           int32_t delta_y) {
+    SwizzleTestWindow();
+    base::ScopedCFTypeRef<CGEventRef> scroll(
+        CGEventCreateScrollWheelEvent(NULL,
+                                      kCGScrollEventUnitLine,
+                                      2,
+                                      delta_y,
+                                      delta_x));
+    // CGEvents are always in global display coordinates. These are like screen
+    // coordinates, but flipped. But first the point needs to be converted out
+    // of window coordinates (which also requires flipping).
+    NSPoint window_point =
+        NSPointFromCGPoint(Flip(window_location).ToCGPoint());
+    NSPoint screen_point = [test_window() convertBaseToScreen:window_point];
+    CGFloat primary_screen_height =
+        NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
+    screen_point.y = primary_screen_height - screen_point.y;
+    CGEventSetLocation(scroll, NSPointToCGPoint(screen_point));
+    return [NSEvent eventWithCGEvent:scroll];
+  }
+
+ private:
+  scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzler_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventsMacTest);
+};
+
+}  // namespace
+
+TEST_F(EventsMacTest, EventFlagsFromNative) {
+  // Left click.
+  NSEvent* left = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON, EventFlagsFromNative(left));
+
+  // Right click.
+  NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp,
+                                                              0);
+  EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right));
+
+  // Middle click.
+  NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp,
+                                                               0);
+  EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle));
+
+  // Caps + Left
+  NSEvent* caps = cocoa_test_event_utils::MouseEventWithType(
+      NSLeftMouseUp, NSAlphaShiftKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CAPS_LOCK_DOWN,
+            EventFlagsFromNative(caps));
+
+  // Shift + Left
+  NSEvent* shift = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
+                                                              NSShiftKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN, EventFlagsFromNative(shift));
+
+  // Ctrl + Left
+  NSEvent* ctrl = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
+                                                             NSControlKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CONTROL_DOWN, EventFlagsFromNative(ctrl));
+
+  // Alt + Left
+  NSEvent* alt = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
+                                                            NSAlternateKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_ALT_DOWN, EventFlagsFromNative(alt));
+
+  // Cmd + Left
+  NSEvent* cmd = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
+                                                            NSCommandKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN, EventFlagsFromNative(cmd));
+
+  // Shift + Ctrl + Left
+  NSEvent* shiftctrl = cocoa_test_event_utils::MouseEventWithType(
+      NSLeftMouseUp, NSShiftKeyMask | NSControlKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN | EF_CONTROL_DOWN,
+            EventFlagsFromNative(shiftctrl));
+
+  // Cmd + Alt + Right
+  NSEvent* cmdalt = cocoa_test_event_utils::MouseEventWithType(
+      NSLeftMouseUp, NSCommandKeyMask | NSAlternateKeyMask);
+  EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN | EF_ALT_DOWN,
+            EventFlagsFromNative(cmdalt));
+}
+
+// Tests mouse button presses and mouse wheel events.
+TEST_F(EventsMacTest, ButtonEvents) {
+  gfx::Point location(5, 10);
+  gfx::Vector2d offset;
+
+  NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event));
+  EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+
+  SwizzleMiddleMouseButton();
+  event = TestMouseEvent(NSOtherMouseDown, location, NSShiftKeyMask);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event));
+  EXPECT_EQ(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_SHIFT_DOWN,
+            ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+  ClearSwizzle();
+
+  event = TestMouseEvent(NSRightMouseUp, location, 0);
+  EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(event));
+  EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+
+  // Scroll up.
+  event = TestScrollEvent(location, 0, 1);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location.ToString(), ui::EventLocationFromNative(event).ToString());
+  offset = ui::GetMouseWheelOffset(event);
+  EXPECT_GT(offset.y(), 0);
+  EXPECT_EQ(0, offset.x());
+  ClearSwizzle();
+
+  // Scroll down.
+  event = TestScrollEvent(location, 0, -1);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+  offset = ui::GetMouseWheelOffset(event);
+  EXPECT_LT(offset.y(), 0);
+  EXPECT_EQ(0, offset.x());
+  ClearSwizzle();
+
+  // Scroll left.
+  event = TestScrollEvent(location, 1, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+  offset = ui::GetMouseWheelOffset(event);
+  EXPECT_EQ(0, offset.y());
+  EXPECT_GT(offset.x(), 0);
+  ClearSwizzle();
+
+  // Scroll right.
+  event = TestScrollEvent(location, -1, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+  offset = ui::GetMouseWheelOffset(event);
+  EXPECT_EQ(0, offset.y());
+  EXPECT_LT(offset.x(), 0);
+  ClearSwizzle();
+}
+
+// Test correct location when the window has a native titlebar.
+TEST_F(EventsMacTest, NativeTitlebarEventLocation) {
+  gfx::Point location(5, 10);
+  NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
+                          NSMiniaturizableWindowMask | NSResizableWindowMask;
+
+  // First check that the window provided by ui::CocoaTest is how we think.
+  DCHECK_EQ(NSBorderlessWindowMask, [test_window() styleMask]);
+  [test_window() setStyleMask:style_mask];
+  DCHECK_EQ(style_mask, [test_window() styleMask]);
+
+  // EventLocationFromNative should behave the same as the ButtonEvents test.
+  NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event));
+  EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(event));
+
+  // And be explicit, to ensure the test doesn't depend on some property of the
+  // test harness. The change to the frame rect could be OS-specfic, so set it
+  // to a known value.
+  const CGFloat kTestHeight = 400;
+  NSRect content_rect = NSMakeRect(0, 0, 600, kTestHeight);
+  NSRect frame_rect = [test_window() frameRectForContentRect:content_rect];
+  [test_window() setFrame:frame_rect display:YES];
+  event = [NSEvent mouseEventWithType:NSLeftMouseDown
+                             location:NSMakePoint(0, 0)  // Bottom-left corner.
+                        modifierFlags:0
+                            timestamp:0
+                         windowNumber:[test_window() windowNumber]
+                              context:nil
+                          eventNumber:0
+                           clickCount:0
+                             pressure:1.0];
+  // Bottom-left corner should be flipped.
+  EXPECT_EQ(gfx::Point(0, kTestHeight), ui::EventLocationFromNative(event));
+
+  // Removing the border, and sending the same event should move it down in the
+  // toolkit-views coordinate system.
+  int height_change = NSHeight(frame_rect) - kTestHeight;
+  EXPECT_GT(height_change, 0);
+  [test_window() setStyleMask:NSBorderlessWindowMask];
+  [test_window() setFrame:frame_rect display:YES];
+  EXPECT_EQ(gfx::Point(0, kTestHeight + height_change),
+            ui::EventLocationFromNative(event));
+}
+
+// Testing for ui::EventTypeFromNative() not covered by ButtonEvents.
+TEST_F(EventsMacTest, EventTypeFromNative) {
+  NSEvent* event = cocoa_test_event_utils::KeyEventWithType(NSKeyDown, 0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(event));
+
+  event = cocoa_test_event_utils::KeyEventWithType(NSKeyUp, 0);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, ui::EventTypeFromNative(event));
+
+  event = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseDragged, 0);
+  EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event));
+  event = cocoa_test_event_utils::MouseEventWithType(NSRightMouseDragged, 0);
+  EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event));
+  event = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseDragged, 0);
+  EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event));
+
+  event = cocoa_test_event_utils::MouseEventWithType(NSMouseMoved, 0);
+  EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(event));
+
+  event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseEntered);
+  EXPECT_EQ(ui::ET_MOUSE_ENTERED, ui::EventTypeFromNative(event));
+  event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseExited);
+  EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(event));
+}
+
+}  // namespace ui
diff --git a/ui/events/device_data_manager.cc b/ui/events/device_data_manager.cc
new file mode 100644
index 0000000..d255b87
--- /dev/null
+++ b/ui/events/device_data_manager.cc
@@ -0,0 +1,129 @@
+// Copyright 2014 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 "ui/events/device_data_manager.h"
+
+#include "base/at_exit.h"
+#include "base/bind.h"
+#include "base/logging.h"
+#include "ui/events/input_device_event_observer.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/geometry/point3_f.h"
+
+namespace ui {
+
+// static
+DeviceDataManager* DeviceDataManager::instance_ = NULL;
+
+DeviceDataManager::DeviceDataManager() {
+  CHECK(!instance_) << "Can not create multiple instances of DeviceDataManager";
+  instance_ = this;
+
+  base::AtExitManager::RegisterTask(
+      base::Bind(&base::DeletePointer<DeviceDataManager>, this));
+
+  for (int i = 0; i < kMaxDeviceNum; ++i) {
+    touch_device_to_display_map_[i] = gfx::Display::kInvalidDisplayID;
+    touch_radius_scale_map_[i] = 1.0;
+  }
+}
+
+DeviceDataManager::~DeviceDataManager() {
+  CHECK_EQ(this, instance_);
+  instance_ = NULL;
+}
+
+// static
+DeviceDataManager* DeviceDataManager::instance() { return instance_; }
+
+// static
+void DeviceDataManager::CreateInstance() {
+  if (instance())
+    return;
+
+  new DeviceDataManager();
+}
+
+// static
+DeviceDataManager* DeviceDataManager::GetInstance() {
+  CHECK(instance_) << "DeviceDataManager was not created.";
+  return instance_;
+}
+
+// static
+bool DeviceDataManager::HasInstance() {
+  return instance_ != NULL;
+}
+
+void DeviceDataManager::ClearTouchTransformerRecord() {
+  for (int i = 0; i < kMaxDeviceNum; i++) {
+    touch_device_transformer_map_[i] = gfx::Transform();
+    touch_device_to_display_map_[i] = gfx::Display::kInvalidDisplayID;
+    touch_radius_scale_map_[i] = 1.0;
+  }
+}
+
+bool DeviceDataManager::IsTouchDeviceIdValid(int touch_device_id) const {
+  return (touch_device_id > 0 && touch_device_id < kMaxDeviceNum);
+}
+
+void DeviceDataManager::UpdateTouchInfoForDisplay(
+    int64_t display_id,
+    int touch_device_id,
+    const gfx::Transform& touch_transformer) {
+  if (IsTouchDeviceIdValid(touch_device_id)) {
+    touch_device_to_display_map_[touch_device_id] = display_id;
+    touch_device_transformer_map_[touch_device_id] = touch_transformer;
+  }
+}
+
+void DeviceDataManager::UpdateTouchRadiusScale(int touch_device_id,
+                                               double scale) {
+  if (IsTouchDeviceIdValid(touch_device_id))
+    touch_radius_scale_map_[touch_device_id] = scale;
+}
+
+void DeviceDataManager::ApplyTouchRadiusScale(int touch_device_id,
+                                              double* radius) {
+  if (IsTouchDeviceIdValid(touch_device_id))
+    *radius = (*radius) * touch_radius_scale_map_[touch_device_id];
+}
+
+void DeviceDataManager::ApplyTouchTransformer(int touch_device_id,
+                                              float* x,
+                                              float* y) {
+  if (IsTouchDeviceIdValid(touch_device_id)) {
+    gfx::Point3F point(*x, *y, 0.0);
+    const gfx::Transform& trans =
+        touch_device_transformer_map_[touch_device_id];
+    trans.TransformPoint(&point);
+    *x = point.x();
+    *y = point.y();
+  }
+}
+
+int64_t DeviceDataManager::GetDisplayForTouchDevice(int touch_device_id) const {
+  if (IsTouchDeviceIdValid(touch_device_id))
+    return touch_device_to_display_map_[touch_device_id];
+  return gfx::Display::kInvalidDisplayID;
+}
+
+void DeviceDataManager::OnTouchscreenDevicesUpdated(
+    const std::vector<TouchscreenDevice>& devices) {
+  touchscreen_devices_ = devices;
+
+  FOR_EACH_OBSERVER(InputDeviceEventObserver,
+                    observers_,
+                    OnInputDeviceConfigurationChanged());
+}
+
+void DeviceDataManager::AddObserver(InputDeviceEventObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void DeviceDataManager::RemoveObserver(InputDeviceEventObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+}  // namespace ui
diff --git a/ui/events/device_data_manager.h b/ui/events/device_data_manager.h
new file mode 100644
index 0000000..aae8272
--- /dev/null
+++ b/ui/events/device_data_manager.h
@@ -0,0 +1,82 @@
+// Copyright 2014 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 UI_EVENTS_DEVICE_DATA_MANAGER_H_
+#define UI_EVENTS_DEVICE_DATA_MANAGER_H_
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/events/device_hotplug_event_observer.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/touchscreen_device.h"
+#include "ui/gfx/transform.h"
+
+namespace ui {
+
+class InputDeviceEventObserver;
+
+// Keeps track of device mappings and event transformations.
+class EVENTS_BASE_EXPORT DeviceDataManager : public DeviceHotplugEventObserver {
+ public:
+  virtual ~DeviceDataManager();
+
+  static void CreateInstance();
+  static DeviceDataManager* GetInstance();
+  static bool HasInstance();
+
+  void ClearTouchTransformerRecord();
+  void UpdateTouchInfoForDisplay(int64_t display_id,
+                                 int touch_device_id,
+                                 const gfx::Transform& touch_transformer);
+  void ApplyTouchTransformer(int touch_device_id, float* x, float* y);
+  int64_t GetDisplayForTouchDevice(int touch_device_id) const;
+
+  void UpdateTouchRadiusScale(int touch_device_id, double scale);
+  void ApplyTouchRadiusScale(int touch_device_id, double* radius);
+
+  const std::vector<TouchscreenDevice>& touchscreen_devices() const {
+    return touchscreen_devices_;
+  }
+
+  void AddObserver(InputDeviceEventObserver* observer);
+  void RemoveObserver(InputDeviceEventObserver* observer);
+
+ protected:
+  DeviceDataManager();
+
+  static DeviceDataManager* instance();
+
+  static const int kMaxDeviceNum = 128;
+
+ private:
+  static DeviceDataManager* instance_;
+
+  bool IsTouchDeviceIdValid(int touch_device_id) const;
+
+  // DeviceHotplugEventObserver:
+  virtual void OnTouchscreenDevicesUpdated(
+      const std::vector<TouchscreenDevice>& devices) OVERRIDE;
+
+  double touch_radius_scale_map_[kMaxDeviceNum];
+
+  // Table to keep track of which display id is mapped to which touch device.
+  int64_t touch_device_to_display_map_[kMaxDeviceNum];
+  // Index table to find the TouchTransformer for a touch device.
+  gfx::Transform touch_device_transformer_map_[kMaxDeviceNum];
+
+  std::vector<TouchscreenDevice> touchscreen_devices_;
+
+  ObserverList<InputDeviceEventObserver> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceDataManager);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_DEVICE_DATA_MANAGER_H_
diff --git a/ui/events/device_hotplug_event_observer.h b/ui/events/device_hotplug_event_observer.h
new file mode 100644
index 0000000..8a763a6
--- /dev/null
+++ b/ui/events/device_hotplug_event_observer.h
@@ -0,0 +1,25 @@
+// Copyright 2014 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 UI_EVENTS_DEVICE_HOTPLUG_EVENT_OBSERVER_H_
+#define UI_EVENTS_DEVICE_HOTPLUG_EVENT_OBSERVER_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/events/touchscreen_device.h"
+
+namespace ui {
+
+// Listener for specific input device hotplug events.
+class EVENTS_BASE_EXPORT DeviceHotplugEventObserver {
+ public:
+  virtual ~DeviceHotplugEventObserver() {}
+
+  // On a hotplug event this is called with the list of available devices.
+  virtual void OnTouchscreenDevicesUpdated(
+      const std::vector<TouchscreenDevice>& devices) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_DEVICE_HOTPLUG_EVENT_OBSERVER_H_
diff --git a/ui/events/event.cc b/ui/events/event.cc
new file mode 100644
index 0000000..72e3bd6
--- /dev/null
+++ b/ui/events/event.cc
@@ -0,0 +1,970 @@
+// Copyright (c) 2012 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 "ui/events/event.h"
+
+#if defined(USE_X11)
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#endif
+
+#include <cmath>
+#include <cstring>
+
+#include "base/metrics/histogram.h"
+#include "base/strings/stringprintf.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/point_conversions.h"
+#include "ui/gfx/transform.h"
+#include "ui/gfx/transform_util.h"
+
+#if defined(USE_X11)
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#elif defined(USE_OZONE)
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#endif
+
+namespace {
+
+std::string EventTypeName(ui::EventType type) {
+#define RETURN_IF_TYPE(t) if (type == ui::t)  return #t
+#define CASE_TYPE(t) case ui::t:  return #t
+  switch (type) {
+    CASE_TYPE(ET_UNKNOWN);
+    CASE_TYPE(ET_MOUSE_PRESSED);
+    CASE_TYPE(ET_MOUSE_DRAGGED);
+    CASE_TYPE(ET_MOUSE_RELEASED);
+    CASE_TYPE(ET_MOUSE_MOVED);
+    CASE_TYPE(ET_MOUSE_ENTERED);
+    CASE_TYPE(ET_MOUSE_EXITED);
+    CASE_TYPE(ET_KEY_PRESSED);
+    CASE_TYPE(ET_KEY_RELEASED);
+    CASE_TYPE(ET_MOUSEWHEEL);
+    CASE_TYPE(ET_MOUSE_CAPTURE_CHANGED);
+    CASE_TYPE(ET_TOUCH_RELEASED);
+    CASE_TYPE(ET_TOUCH_PRESSED);
+    CASE_TYPE(ET_TOUCH_MOVED);
+    CASE_TYPE(ET_TOUCH_CANCELLED);
+    CASE_TYPE(ET_DROP_TARGET_EVENT);
+    CASE_TYPE(ET_TRANSLATED_KEY_PRESS);
+    CASE_TYPE(ET_TRANSLATED_KEY_RELEASE);
+    CASE_TYPE(ET_GESTURE_SCROLL_BEGIN);
+    CASE_TYPE(ET_GESTURE_SCROLL_END);
+    CASE_TYPE(ET_GESTURE_SCROLL_UPDATE);
+    CASE_TYPE(ET_GESTURE_SHOW_PRESS);
+    CASE_TYPE(ET_GESTURE_WIN8_EDGE_SWIPE);
+    CASE_TYPE(ET_GESTURE_TAP);
+    CASE_TYPE(ET_GESTURE_TAP_DOWN);
+    CASE_TYPE(ET_GESTURE_TAP_CANCEL);
+    CASE_TYPE(ET_GESTURE_BEGIN);
+    CASE_TYPE(ET_GESTURE_END);
+    CASE_TYPE(ET_GESTURE_TWO_FINGER_TAP);
+    CASE_TYPE(ET_GESTURE_PINCH_BEGIN);
+    CASE_TYPE(ET_GESTURE_PINCH_END);
+    CASE_TYPE(ET_GESTURE_PINCH_UPDATE);
+    CASE_TYPE(ET_GESTURE_LONG_PRESS);
+    CASE_TYPE(ET_GESTURE_LONG_TAP);
+    CASE_TYPE(ET_GESTURE_SWIPE);
+    CASE_TYPE(ET_GESTURE_TAP_UNCONFIRMED);
+    CASE_TYPE(ET_GESTURE_DOUBLE_TAP);
+    CASE_TYPE(ET_SCROLL);
+    CASE_TYPE(ET_SCROLL_FLING_START);
+    CASE_TYPE(ET_SCROLL_FLING_CANCEL);
+    CASE_TYPE(ET_CANCEL_MODE);
+    CASE_TYPE(ET_UMA_DATA);
+    case ui::ET_LAST: NOTREACHED(); return std::string();
+    // Don't include default, so that we get an error when new type is added.
+  }
+#undef CASE_TYPE
+
+  NOTREACHED();
+  return std::string();
+}
+
+bool IsX11SendEventTrue(const base::NativeEvent& event) {
+#if defined(USE_X11)
+  return event && event->xany.send_event;
+#else
+  return false;
+#endif
+}
+
+bool X11EventHasNonStandardState(const base::NativeEvent& event) {
+#if defined(USE_X11)
+  const unsigned int kAllStateMask =
+      Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask |
+      Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask | ShiftMask |
+      LockMask | ControlMask | AnyModifier;
+  return event && (event->xkey.state & ~kAllStateMask) != 0;
+#else
+  return false;
+#endif
+}
+
+}  // namespace
+
+namespace ui {
+
+////////////////////////////////////////////////////////////////////////////////
+// Event
+
+// static
+scoped_ptr<Event> Event::Clone(const Event& event) {
+  if (event.IsKeyEvent()) {
+    return scoped_ptr<Event>(new KeyEvent(static_cast<const KeyEvent&>(event)));
+  }
+
+  if (event.IsMouseEvent()) {
+    if (event.IsMouseWheelEvent()) {
+      return scoped_ptr<Event>(
+          new MouseWheelEvent(static_cast<const MouseWheelEvent&>(event)));
+    }
+
+    return scoped_ptr<Event>(
+        new MouseEvent(static_cast<const MouseEvent&>(event)));
+  }
+
+  if (event.IsTouchEvent()) {
+    return scoped_ptr<Event>(
+        new TouchEvent(static_cast<const TouchEvent&>(event)));
+  }
+
+  if (event.IsGestureEvent()) {
+    return scoped_ptr<Event>(
+        new GestureEvent(static_cast<const GestureEvent&>(event)));
+  }
+
+  if (event.IsScrollEvent()) {
+    return scoped_ptr<Event>(
+        new ScrollEvent(static_cast<const ScrollEvent&>(event)));
+  }
+
+  return scoped_ptr<Event>(new Event(event));
+}
+
+Event::~Event() {
+  if (delete_native_event_)
+    ReleaseCopiedNativeEvent(native_event_);
+}
+
+GestureEvent* Event::AsGestureEvent() {
+  CHECK(IsGestureEvent());
+  return static_cast<GestureEvent*>(this);
+}
+
+const GestureEvent* Event::AsGestureEvent() const {
+  CHECK(IsGestureEvent());
+  return static_cast<const GestureEvent*>(this);
+}
+
+bool Event::HasNativeEvent() const {
+  base::NativeEvent null_event;
+  std::memset(&null_event, 0, sizeof(null_event));
+  return !!std::memcmp(&native_event_, &null_event, sizeof(null_event));
+}
+
+void Event::StopPropagation() {
+  // TODO(sad): Re-enable these checks once View uses dispatcher to dispatch
+  // events.
+  // CHECK(phase_ != EP_PREDISPATCH && phase_ != EP_POSTDISPATCH);
+  CHECK(cancelable_);
+  result_ = static_cast<EventResult>(result_ | ER_CONSUMED);
+}
+
+void Event::SetHandled() {
+  // TODO(sad): Re-enable these checks once View uses dispatcher to dispatch
+  // events.
+  // CHECK(phase_ != EP_PREDISPATCH && phase_ != EP_POSTDISPATCH);
+  CHECK(cancelable_);
+  result_ = static_cast<EventResult>(result_ | ER_HANDLED);
+}
+
+Event::Event(EventType type, base::TimeDelta time_stamp, int flags)
+    : type_(type),
+      time_stamp_(time_stamp),
+      flags_(flags),
+      native_event_(base::NativeEvent()),
+      delete_native_event_(false),
+      cancelable_(true),
+      target_(NULL),
+      phase_(EP_PREDISPATCH),
+      result_(ER_UNHANDLED),
+      source_device_id_(ED_UNKNOWN_DEVICE) {
+  if (type_ < ET_LAST)
+    name_ = EventTypeName(type_);
+}
+
+Event::Event(const base::NativeEvent& native_event,
+             EventType type,
+             int flags)
+    : type_(type),
+      time_stamp_(EventTimeFromNative(native_event)),
+      flags_(flags),
+      native_event_(native_event),
+      delete_native_event_(false),
+      cancelable_(true),
+      target_(NULL),
+      phase_(EP_PREDISPATCH),
+      result_(ER_UNHANDLED),
+      source_device_id_(ED_UNKNOWN_DEVICE) {
+  base::TimeDelta delta = EventTimeForNow() - time_stamp_;
+  if (type_ < ET_LAST)
+    name_ = EventTypeName(type_);
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser",
+                              delta.InMicroseconds(), 1, 1000000, 100);
+  std::string name_for_event =
+      base::StringPrintf("Event.Latency.Browser.%s", name_.c_str());
+  base::HistogramBase* counter_for_type =
+      base::Histogram::FactoryGet(
+          name_for_event,
+          1,
+          1000000,
+          100,
+          base::HistogramBase::kUmaTargetedHistogramFlag);
+  counter_for_type->Add(delta.InMicroseconds());
+
+#if defined(USE_X11)
+  if (native_event->type == GenericEvent) {
+    XIDeviceEvent* xiev =
+        static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+    source_device_id_ = xiev->sourceid;
+  }
+#endif
+}
+
+Event::Event(const Event& copy)
+    : type_(copy.type_),
+      time_stamp_(copy.time_stamp_),
+      latency_(copy.latency_),
+      flags_(copy.flags_),
+      native_event_(CopyNativeEvent(copy.native_event_)),
+      delete_native_event_(true),
+      cancelable_(true),
+      target_(NULL),
+      phase_(EP_PREDISPATCH),
+      result_(ER_UNHANDLED),
+      source_device_id_(copy.source_device_id_) {
+  if (type_ < ET_LAST)
+    name_ = EventTypeName(type_);
+}
+
+void Event::SetType(EventType type) {
+  if (type_ < ET_LAST)
+    name_ = std::string();
+  type_ = type;
+  if (type_ < ET_LAST)
+    name_ = EventTypeName(type_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// CancelModeEvent
+
+CancelModeEvent::CancelModeEvent()
+    : Event(ET_CANCEL_MODE, base::TimeDelta(), 0) {
+  set_cancelable(false);
+}
+
+CancelModeEvent::~CancelModeEvent() {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LocatedEvent
+
+LocatedEvent::~LocatedEvent() {
+}
+
+LocatedEvent::LocatedEvent(const base::NativeEvent& native_event)
+    : Event(native_event,
+            EventTypeFromNative(native_event),
+            EventFlagsFromNative(native_event)),
+      location_(EventLocationFromNative(native_event)),
+      root_location_(location_) {
+}
+
+LocatedEvent::LocatedEvent(EventType type,
+                           const gfx::PointF& location,
+                           const gfx::PointF& root_location,
+                           base::TimeDelta time_stamp,
+                           int flags)
+    : Event(type, time_stamp, flags),
+      location_(location),
+      root_location_(root_location) {
+}
+
+void LocatedEvent::UpdateForRootTransform(
+    const gfx::Transform& reversed_root_transform) {
+  // Transform has to be done at root level.
+  gfx::Point3F p(location_);
+  reversed_root_transform.TransformPoint(&p);
+  location_ = p.AsPointF();
+  root_location_ = location_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseEvent
+
+MouseEvent::MouseEvent(const base::NativeEvent& native_event)
+    : LocatedEvent(native_event),
+      changed_button_flags_(
+          GetChangedMouseButtonFlagsFromNative(native_event)) {
+  if (type() == ET_MOUSE_PRESSED || type() == ET_MOUSE_RELEASED)
+    SetClickCount(GetRepeatCount(*this));
+}
+
+MouseEvent::MouseEvent(EventType type,
+                       const gfx::PointF& location,
+                       const gfx::PointF& root_location,
+                       int flags,
+                       int changed_button_flags)
+    : LocatedEvent(type, location, root_location, EventTimeForNow(), flags),
+      changed_button_flags_(changed_button_flags) {
+  if (this->type() == ET_MOUSE_MOVED && IsAnyButton())
+    SetType(ET_MOUSE_DRAGGED);
+}
+
+// static
+bool MouseEvent::IsRepeatedClickEvent(
+    const MouseEvent& event1,
+    const MouseEvent& event2) {
+  // These values match the Windows defaults.
+  static const int kDoubleClickTimeMS = 500;
+  static const int kDoubleClickWidth = 4;
+  static const int kDoubleClickHeight = 4;
+
+  if (event1.type() != ET_MOUSE_PRESSED ||
+      event2.type() != ET_MOUSE_PRESSED)
+    return false;
+
+  // Compare flags, but ignore EF_IS_DOUBLE_CLICK to allow triple clicks.
+  if ((event1.flags() & ~EF_IS_DOUBLE_CLICK) !=
+      (event2.flags() & ~EF_IS_DOUBLE_CLICK))
+    return false;
+
+  base::TimeDelta time_difference = event2.time_stamp() - event1.time_stamp();
+
+  if (time_difference.InMilliseconds() > kDoubleClickTimeMS)
+    return false;
+
+  if (std::abs(event2.x() - event1.x()) > kDoubleClickWidth / 2)
+    return false;
+
+  if (std::abs(event2.y() - event1.y()) > kDoubleClickHeight / 2)
+    return false;
+
+  return true;
+}
+
+// static
+int MouseEvent::GetRepeatCount(const MouseEvent& event) {
+  int click_count = 1;
+  if (last_click_event_) {
+    if (event.type() == ui::ET_MOUSE_RELEASED) {
+      if (event.changed_button_flags() ==
+              last_click_event_->changed_button_flags()) {
+        last_click_complete_ = true;
+        return last_click_event_->GetClickCount();
+      } else {
+        // If last_click_event_ has changed since this button was pressed
+        // return a click count of 1.
+        return click_count;
+      }
+    }
+    if (event.time_stamp() != last_click_event_->time_stamp())
+      last_click_complete_ = true;
+    if (!last_click_complete_ ||
+        IsX11SendEventTrue(event.native_event())) {
+      click_count = last_click_event_->GetClickCount();
+    } else if (IsRepeatedClickEvent(*last_click_event_, event)) {
+      click_count = last_click_event_->GetClickCount() + 1;
+    }
+    delete last_click_event_;
+  }
+  last_click_event_ = new MouseEvent(event);
+  last_click_complete_ = false;
+  if (click_count > 3)
+    click_count = 3;
+  last_click_event_->SetClickCount(click_count);
+  return click_count;
+}
+
+void MouseEvent::ResetLastClickForTest() {
+  if (last_click_event_) {
+    delete last_click_event_;
+    last_click_event_ = NULL;
+    last_click_complete_ = false;
+  }
+}
+
+// static
+MouseEvent* MouseEvent::last_click_event_ = NULL;
+bool MouseEvent::last_click_complete_ = false;
+
+int MouseEvent::GetClickCount() const {
+  if (type() != ET_MOUSE_PRESSED && type() != ET_MOUSE_RELEASED)
+    return 0;
+
+  if (flags() & EF_IS_TRIPLE_CLICK)
+    return 3;
+  else if (flags() & EF_IS_DOUBLE_CLICK)
+    return 2;
+  else
+    return 1;
+}
+
+void MouseEvent::SetClickCount(int click_count) {
+  if (type() != ET_MOUSE_PRESSED && type() != ET_MOUSE_RELEASED)
+    return;
+
+  DCHECK(click_count > 0);
+  DCHECK(click_count <= 3);
+
+  int f = flags();
+  switch (click_count) {
+    case 1:
+      f &= ~EF_IS_DOUBLE_CLICK;
+      f &= ~EF_IS_TRIPLE_CLICK;
+      break;
+    case 2:
+      f |= EF_IS_DOUBLE_CLICK;
+      f &= ~EF_IS_TRIPLE_CLICK;
+      break;
+    case 3:
+      f &= ~EF_IS_DOUBLE_CLICK;
+      f |= EF_IS_TRIPLE_CLICK;
+      break;
+  }
+  set_flags(f);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseWheelEvent
+
+MouseWheelEvent::MouseWheelEvent(const base::NativeEvent& native_event)
+    : MouseEvent(native_event),
+      offset_(GetMouseWheelOffset(native_event)) {
+}
+
+MouseWheelEvent::MouseWheelEvent(const ScrollEvent& scroll_event)
+    : MouseEvent(scroll_event),
+      offset_(scroll_event.x_offset(), scroll_event.y_offset()){
+  SetType(ET_MOUSEWHEEL);
+}
+
+MouseWheelEvent::MouseWheelEvent(const MouseEvent& mouse_event,
+                                 int x_offset,
+                                 int y_offset)
+    : MouseEvent(mouse_event), offset_(x_offset, y_offset) {
+  DCHECK(type() == ET_MOUSEWHEEL);
+}
+
+MouseWheelEvent::MouseWheelEvent(const MouseWheelEvent& mouse_wheel_event)
+    : MouseEvent(mouse_wheel_event),
+      offset_(mouse_wheel_event.offset()) {
+  DCHECK(type() == ET_MOUSEWHEEL);
+}
+
+MouseWheelEvent::MouseWheelEvent(const gfx::Vector2d& offset,
+                                 const gfx::PointF& location,
+                                 const gfx::PointF& root_location,
+                                 int flags,
+                                 int changed_button_flags)
+    : MouseEvent(ui::ET_MOUSEWHEEL, location, root_location, flags,
+                 changed_button_flags),
+      offset_(offset) {
+}
+
+#if defined(OS_WIN)
+// This value matches windows WHEEL_DELTA.
+// static
+const int MouseWheelEvent::kWheelDelta = 120;
+#else
+// This value matches GTK+ wheel scroll amount.
+const int MouseWheelEvent::kWheelDelta = 53;
+#endif
+
+void MouseWheelEvent::UpdateForRootTransform(
+    const gfx::Transform& inverted_root_transform) {
+  LocatedEvent::UpdateForRootTransform(inverted_root_transform);
+  gfx::DecomposedTransform decomp;
+  bool success = gfx::DecomposeTransform(&decomp, inverted_root_transform);
+  DCHECK(success);
+  if (decomp.scale[0])
+    offset_.set_x(offset_.x() * decomp.scale[0]);
+  if (decomp.scale[1])
+    offset_.set_y(offset_.y() * decomp.scale[1]);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEvent
+
+TouchEvent::TouchEvent(const base::NativeEvent& native_event)
+    : LocatedEvent(native_event),
+      touch_id_(GetTouchId(native_event)),
+      radius_x_(GetTouchRadiusX(native_event)),
+      radius_y_(GetTouchRadiusY(native_event)),
+      rotation_angle_(GetTouchAngle(native_event)),
+      force_(GetTouchForce(native_event)) {
+  latency()->AddLatencyNumberWithTimestamp(
+      INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+      0,
+      0,
+      base::TimeTicks::FromInternalValue(time_stamp().ToInternalValue()),
+      1);
+
+  latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+
+  if (type() == ET_TOUCH_PRESSED)
+    IncrementTouchIdRefCount(native_event);
+}
+
+TouchEvent::TouchEvent(EventType type,
+                       const gfx::PointF& location,
+                       int touch_id,
+                       base::TimeDelta time_stamp)
+    : LocatedEvent(type, location, location, time_stamp, 0),
+      touch_id_(touch_id),
+      radius_x_(0.0f),
+      radius_y_(0.0f),
+      rotation_angle_(0.0f),
+      force_(0.0f) {
+  latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+}
+
+TouchEvent::TouchEvent(EventType type,
+                       const gfx::PointF& location,
+                       int flags,
+                       int touch_id,
+                       base::TimeDelta time_stamp,
+                       float radius_x,
+                       float radius_y,
+                       float angle,
+                       float force)
+    : LocatedEvent(type, location, location, time_stamp, flags),
+      touch_id_(touch_id),
+      radius_x_(radius_x),
+      radius_y_(radius_y),
+      rotation_angle_(angle),
+      force_(force) {
+  latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
+}
+
+TouchEvent::~TouchEvent() {
+  // In ctor TouchEvent(native_event) we call GetTouchId() which in X11
+  // platform setups the tracking_id to slot mapping. So in dtor here,
+  // if this touch event is a release event, we clear the mapping accordingly.
+  if (HasNativeEvent())
+    ClearTouchIdIfReleased(native_event());
+}
+
+void TouchEvent::UpdateForRootTransform(
+    const gfx::Transform& inverted_root_transform) {
+  LocatedEvent::UpdateForRootTransform(inverted_root_transform);
+  gfx::DecomposedTransform decomp;
+  bool success = gfx::DecomposeTransform(&decomp, inverted_root_transform);
+  DCHECK(success);
+  if (decomp.scale[0])
+    radius_x_ *= decomp.scale[0];
+  if (decomp.scale[1])
+    radius_y_ *= decomp.scale[1];
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent
+
+// static
+KeyEvent* KeyEvent::last_key_event_ = NULL;
+
+// static
+bool KeyEvent::IsRepeated(const KeyEvent& event) {
+  // A safe guard in case if there were continous key pressed events that are
+  // not auto repeat.
+  const int kMaxAutoRepeatTimeMs = 2000;
+  // Ignore key events that have non standard state masks as it may be
+  // reposted by an IME. IBUS-GTK uses this field to detect the
+  // re-posted event for example. crbug.com/385873.
+  if (X11EventHasNonStandardState(event.native_event()))
+    return false;
+  if (event.is_char())
+    return false;
+  if (event.type() == ui::ET_KEY_RELEASED) {
+    delete last_key_event_;
+    last_key_event_ = NULL;
+    return false;
+  }
+  CHECK_EQ(ui::ET_KEY_PRESSED, event.type());
+  if (!last_key_event_) {
+    last_key_event_ = new KeyEvent(event);
+    return false;
+  }
+  if (event.key_code() == last_key_event_->key_code() &&
+      event.flags() == last_key_event_->flags() &&
+      (event.time_stamp() - last_key_event_->time_stamp()).InMilliseconds() <
+      kMaxAutoRepeatTimeMs) {
+    return true;
+  }
+  delete last_key_event_;
+  last_key_event_ = new KeyEvent(event);
+  return false;
+}
+
+KeyEvent::KeyEvent(const base::NativeEvent& native_event)
+    : Event(native_event,
+            EventTypeFromNative(native_event),
+            EventFlagsFromNative(native_event)),
+      key_code_(KeyboardCodeFromNative(native_event)),
+      code_(CodeFromNative(native_event)),
+      is_char_(IsCharFromNative(native_event)),
+      platform_keycode_(PlatformKeycodeFromNative(native_event)),
+      character_(0) {
+  if (IsRepeated(*this))
+    set_flags(flags() | ui::EF_IS_REPEAT);
+
+#if defined(USE_X11)
+  NormalizeFlags();
+#endif
+#if defined(OS_WIN)
+  // Only Windows has native character events.
+  if (is_char_)
+    character_ = native_event.wParam;
+#endif
+}
+
+KeyEvent::KeyEvent(EventType type,
+                   KeyboardCode key_code,
+                   int flags)
+    : Event(type, EventTimeForNow(), flags),
+      key_code_(key_code),
+      is_char_(false),
+      platform_keycode_(0),
+      character_() {
+}
+
+KeyEvent::KeyEvent(EventType type,
+                   KeyboardCode key_code,
+                   const std::string& code,
+                   int flags)
+    : Event(type, EventTimeForNow(), flags),
+      key_code_(key_code),
+      code_(code),
+      is_char_(false),
+      platform_keycode_(0),
+      character_(0) {
+}
+
+KeyEvent::KeyEvent(base::char16 character, KeyboardCode key_code, int flags)
+    : Event(ET_KEY_PRESSED, EventTimeForNow(), flags),
+      key_code_(key_code),
+      code_(""),
+      is_char_(true),
+      platform_keycode_(0),
+      character_(character) {
+}
+
+KeyEvent::KeyEvent(const KeyEvent& rhs)
+    : Event(rhs),
+      key_code_(rhs.key_code_),
+      code_(rhs.code_),
+      is_char_(rhs.is_char_),
+      platform_keycode_(rhs.platform_keycode_),
+      character_(rhs.character_) {
+  if (rhs.extended_key_event_data_)
+    extended_key_event_data_.reset(rhs.extended_key_event_data_->Clone());
+}
+
+KeyEvent& KeyEvent::operator=(const KeyEvent& rhs) {
+  if (this != &rhs) {
+    Event::operator=(rhs);
+    key_code_ = rhs.key_code_;
+    code_ = rhs.code_;
+    is_char_ = rhs.is_char_;
+    platform_keycode_ = rhs.platform_keycode_;
+    character_ = rhs.character_;
+
+    if (rhs.extended_key_event_data_)
+      extended_key_event_data_.reset(rhs.extended_key_event_data_->Clone());
+  }
+  return *this;
+}
+
+KeyEvent::~KeyEvent() {}
+
+void KeyEvent::SetExtendedKeyEventData(scoped_ptr<ExtendedKeyEventData> data) {
+  extended_key_event_data_ = data.Pass();
+}
+
+base::char16 KeyEvent::GetCharacter() const {
+  if (is_char_ || character_)
+    return character_;
+
+  // TODO(kpschoedel): streamline these cases after settling Ozone
+  // positional coding.
+#if defined(OS_WIN)
+  // Native Windows character events always have is_char_ == true,
+  // so this is a synthetic or native keystroke event.
+  character_ = GetCharacterFromKeyCode(key_code_, flags());
+  return character_;
+#elif defined(USE_X11)
+  if (!native_event()) {
+    character_ = GetCharacterFromKeyCode(key_code_, flags());
+    return character_;
+  }
+
+  DCHECK(native_event()->type == KeyPress ||
+         native_event()->type == KeyRelease ||
+         (native_event()->type == GenericEvent &&
+          (native_event()->xgeneric.evtype == XI_KeyPress ||
+           native_event()->xgeneric.evtype == XI_KeyRelease)));
+
+  // When a control key is held, prefer ASCII characters to non ASCII
+  // characters in order to use it for shortcut keys.  GetCharacterFromKeyCode
+  // returns 'a' for VKEY_A even if the key is actually bound to 'à' in X11.
+  // GetCharacterFromXEvent returns 'à' in that case.
+  return IsControlDown() ?
+      GetCharacterFromKeyCode(key_code_, flags()) :
+      GetCharacterFromXEvent(native_event());
+#else
+  if (native_event()) {
+    DCHECK(EventTypeFromNative(native_event()) == ET_KEY_PRESSED ||
+           EventTypeFromNative(native_event()) == ET_KEY_RELEASED);
+  }
+
+  return GetCharacterFromKeyCode(key_code_, flags());
+#endif
+}
+
+base::char16 KeyEvent::GetText() const {
+  if ((flags() & EF_CONTROL_DOWN) != 0) {
+    return GetControlCharacterForKeycode(key_code_,
+                                         (flags() & EF_SHIFT_DOWN) != 0);
+  }
+  return GetUnmodifiedText();
+}
+
+base::char16 KeyEvent::GetUnmodifiedText() const {
+  if (!is_char_ && (key_code_ == VKEY_RETURN))
+    return '\r';
+  return GetCharacter();
+}
+
+bool KeyEvent::IsUnicodeKeyCode() const {
+#if defined(OS_WIN)
+  if (!IsAltDown())
+    return false;
+  const int key = key_code();
+  if (key >= VKEY_NUMPAD0 && key <= VKEY_NUMPAD9)
+    return true;
+  // Check whether the user is using the numeric keypad with num-lock off.
+  // In that case, EF_EXTENDED will not be set; if it is set, the key event
+  // originated from the relevant non-numpad dedicated key, e.g. [Insert].
+  return (!(flags() & EF_EXTENDED) &&
+          (key == VKEY_INSERT || key == VKEY_END  || key == VKEY_DOWN ||
+           key == VKEY_NEXT   || key == VKEY_LEFT || key == VKEY_CLEAR ||
+           key == VKEY_RIGHT  || key == VKEY_HOME || key == VKEY_UP ||
+           key == VKEY_PRIOR));
+#else
+  return false;
+#endif
+}
+
+void KeyEvent::NormalizeFlags() {
+  int mask = 0;
+  switch (key_code()) {
+    case VKEY_CONTROL:
+      mask = EF_CONTROL_DOWN;
+      break;
+    case VKEY_SHIFT:
+      mask = EF_SHIFT_DOWN;
+      break;
+    case VKEY_MENU:
+      mask = EF_ALT_DOWN;
+      break;
+    case VKEY_CAPITAL:
+      mask = EF_CAPS_LOCK_DOWN;
+      break;
+    default:
+      return;
+  }
+  if (type() == ET_KEY_PRESSED)
+    set_flags(flags() | mask);
+  else
+    set_flags(flags() & ~mask);
+}
+
+bool KeyEvent::IsTranslated() const {
+  switch (type()) {
+    case ET_KEY_PRESSED:
+    case ET_KEY_RELEASED:
+      return false;
+    case ET_TRANSLATED_KEY_PRESS:
+    case ET_TRANSLATED_KEY_RELEASE:
+      return true;
+    default:
+      NOTREACHED();
+      return false;
+  }
+}
+
+void KeyEvent::SetTranslated(bool translated) {
+  switch (type()) {
+    case ET_KEY_PRESSED:
+    case ET_TRANSLATED_KEY_PRESS:
+      SetType(translated ? ET_TRANSLATED_KEY_PRESS : ET_KEY_PRESSED);
+      break;
+    case ET_KEY_RELEASED:
+    case ET_TRANSLATED_KEY_RELEASE:
+      SetType(translated ? ET_TRANSLATED_KEY_RELEASE : ET_KEY_RELEASED);
+      break;
+    default:
+      NOTREACHED();
+  }
+}
+
+bool KeyEvent::IsRightSideKey() const {
+  switch (key_code_) {
+    case VKEY_CONTROL:
+    case VKEY_SHIFT:
+    case VKEY_MENU:
+    case VKEY_LWIN:
+#if defined(USE_X11)
+      // Under X11, setting code_ requires platform-dependent information, and
+      // currently assumes that X keycodes are based on Linux evdev keycodes.
+      // In certain test environments this is not the case, and code_ is not
+      // set accurately, so we need a different mechanism. Fortunately X11 key
+      // mapping preserves the left-right distinction, so testing keysyms works
+      // if the value is available (as it is for all X11 native-based events).
+      if (platform_keycode_) {
+        return (platform_keycode_ == XK_Shift_R) ||
+               (platform_keycode_ == XK_Control_R) ||
+               (platform_keycode_ == XK_Alt_R) ||
+               (platform_keycode_ == XK_Meta_R) ||
+               (platform_keycode_ == XK_Super_R) ||
+               (platform_keycode_ == XK_Hyper_R);
+      }
+      // Fall through to the generic code if we have no platform_keycode_.
+      // Under X11, this must be a synthetic event, so we can require that
+      // code_ be set correctly.
+#endif
+      return ((code_.size() > 5) &&
+              (code_.compare(code_.size() - 5, 5, "Right", 5)) == 0);
+    default:
+      return false;
+  }
+}
+
+KeyboardCode KeyEvent::GetLocatedWindowsKeyboardCode() const {
+  switch (key_code_) {
+    case VKEY_SHIFT:
+      return IsRightSideKey() ? VKEY_RSHIFT : VKEY_LSHIFT;
+    case VKEY_CONTROL:
+      return IsRightSideKey() ? VKEY_RCONTROL : VKEY_LCONTROL;
+    case VKEY_MENU:
+      return IsRightSideKey() ? VKEY_RMENU : VKEY_LMENU;
+    case VKEY_LWIN:
+      return IsRightSideKey() ? VKEY_RWIN : VKEY_LWIN;
+    // TODO(kpschoedel): EF_NUMPAD_KEY is present only on X11. Currently this
+    // function is only called on X11. Likely the tests here will be replaced
+    // with a DOM-based code enumeration test in the course of Ozone
+    // platform-indpendent key event work.
+    case VKEY_0:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD0 : VKEY_0;
+    case VKEY_1:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD1 : VKEY_1;
+    case VKEY_2:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD2 : VKEY_2;
+    case VKEY_3:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD3 : VKEY_3;
+    case VKEY_4:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD4 : VKEY_4;
+    case VKEY_5:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD5 : VKEY_5;
+    case VKEY_6:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD6 : VKEY_6;
+    case VKEY_7:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD7 : VKEY_7;
+    case VKEY_8:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD8 : VKEY_8;
+    case VKEY_9:
+      return (flags() & EF_NUMPAD_KEY) ? VKEY_NUMPAD9 : VKEY_9;
+    default:
+      return key_code_;
+  }
+}
+
+uint16 KeyEvent::GetConflatedWindowsKeyCode() const {
+  if (is_char_)
+    return character_;
+  return key_code_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// ScrollEvent
+
+ScrollEvent::ScrollEvent(const base::NativeEvent& native_event)
+    : MouseEvent(native_event) {
+  if (type() == ET_SCROLL) {
+    GetScrollOffsets(native_event,
+                     &x_offset_, &y_offset_,
+                     &x_offset_ordinal_, &y_offset_ordinal_,
+                     &finger_count_);
+  } else if (type() == ET_SCROLL_FLING_START ||
+             type() == ET_SCROLL_FLING_CANCEL) {
+    GetFlingData(native_event,
+                 &x_offset_, &y_offset_,
+                 &x_offset_ordinal_, &y_offset_ordinal_,
+                 NULL);
+  } else {
+    NOTREACHED() << "Unexpected event type " << type()
+        << " when constructing a ScrollEvent.";
+  }
+}
+
+ScrollEvent::ScrollEvent(EventType type,
+                         const gfx::PointF& location,
+                         base::TimeDelta time_stamp,
+                         int flags,
+                         float x_offset,
+                         float y_offset,
+                         float x_offset_ordinal,
+                         float y_offset_ordinal,
+                         int finger_count)
+    : MouseEvent(type, location, location, flags, 0),
+      x_offset_(x_offset),
+      y_offset_(y_offset),
+      x_offset_ordinal_(x_offset_ordinal),
+      y_offset_ordinal_(y_offset_ordinal),
+      finger_count_(finger_count) {
+  set_time_stamp(time_stamp);
+  CHECK(IsScrollEvent());
+}
+
+void ScrollEvent::Scale(const float factor) {
+  x_offset_ *= factor;
+  y_offset_ *= factor;
+  x_offset_ordinal_ *= factor;
+  y_offset_ordinal_ *= factor;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureEvent
+
+GestureEvent::GestureEvent(float x,
+                           float y,
+                           int flags,
+                           base::TimeDelta time_stamp,
+                           const GestureEventDetails& details)
+    : LocatedEvent(details.type(),
+                   gfx::PointF(x, y),
+                   gfx::PointF(x, y),
+                   time_stamp,
+                   flags | EF_FROM_TOUCH),
+      details_(details) {
+}
+
+GestureEvent::~GestureEvent() {
+}
+
+}  // namespace ui
diff --git a/ui/events/event.h b/ui/events/event.h
new file mode 100644
index 0000000..8f2c79a
--- /dev/null
+++ b/ui/events/event.h
@@ -0,0 +1,815 @@
+// Copyright (c) 2012 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 UI_EVENTS_EVENT_H_
+#define UI_EVENTS_EVENT_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/event_types.h"
+#include "base/gtest_prod_util.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_event_details.h"
+#include "ui/events/gestures/gesture_types.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/latency_info.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/point_conversions.h"
+
+namespace gfx {
+class Transform;
+}
+
+namespace ui {
+class EventTarget;
+
+class EVENTS_EXPORT Event {
+ public:
+  static scoped_ptr<Event> Clone(const Event& event);
+
+  virtual ~Event();
+
+  class DispatcherApi {
+   public:
+    explicit DispatcherApi(Event* event) : event_(event) {}
+
+    void set_target(EventTarget* target) {
+      event_->target_ = target;
+    }
+
+    void set_phase(EventPhase phase) { event_->phase_ = phase; }
+    void set_result(int result) {
+      event_->result_ = static_cast<EventResult>(result);
+    }
+
+   private:
+    DispatcherApi();
+    Event* event_;
+
+    DISALLOW_COPY_AND_ASSIGN(DispatcherApi);
+  };
+
+  const base::NativeEvent& native_event() const { return native_event_; }
+  EventType type() const { return type_; }
+  const std::string& name() const { return name_; }
+  // time_stamp represents time since machine was booted.
+  const base::TimeDelta& time_stamp() const { return time_stamp_; }
+  int flags() const { return flags_; }
+
+  // This is only intended to be used externally by classes that are modifying
+  // events in an EventRewriter.
+  void set_flags(int flags) { flags_ = flags; }
+
+  EventTarget* target() const { return target_; }
+  EventPhase phase() const { return phase_; }
+  EventResult result() const { return result_; }
+
+  LatencyInfo* latency() { return &latency_; }
+  const LatencyInfo* latency() const { return &latency_; }
+  void set_latency(const LatencyInfo& latency) { latency_ = latency; }
+
+  int source_device_id() const { return source_device_id_; }
+
+  // By default, events are "cancelable", this means any default processing that
+  // the containing abstraction layer may perform can be prevented by calling
+  // SetHandled(). SetHandled() or StopPropagation() must not be called for
+  // events that are not cancelable.
+  bool cancelable() const { return cancelable_; }
+
+  // The following methods return true if the respective keys were pressed at
+  // the time the event was created.
+  bool IsShiftDown() const { return (flags_ & EF_SHIFT_DOWN) != 0; }
+  bool IsControlDown() const { return (flags_ & EF_CONTROL_DOWN) != 0; }
+  bool IsCapsLockDown() const { return (flags_ & EF_CAPS_LOCK_DOWN) != 0; }
+  bool IsAltDown() const { return (flags_ & EF_ALT_DOWN) != 0; }
+  bool IsAltGrDown() const { return (flags_ & EF_ALTGR_DOWN) != 0; }
+  bool IsCommandDown() const { return (flags_ & EF_COMMAND_DOWN) != 0; }
+  bool IsRepeat() const { return (flags_ & EF_IS_REPEAT) != 0; }
+
+  bool IsKeyEvent() const {
+    return type_ == ET_KEY_PRESSED ||
+           type_ == ET_KEY_RELEASED ||
+           type_ == ET_TRANSLATED_KEY_PRESS ||
+           type_ == ET_TRANSLATED_KEY_RELEASE;
+  }
+
+  bool IsMouseEvent() const {
+    return type_ == ET_MOUSE_PRESSED ||
+           type_ == ET_MOUSE_DRAGGED ||
+           type_ == ET_MOUSE_RELEASED ||
+           type_ == ET_MOUSE_MOVED ||
+           type_ == ET_MOUSE_ENTERED ||
+           type_ == ET_MOUSE_EXITED ||
+           type_ == ET_MOUSEWHEEL ||
+           type_ == ET_MOUSE_CAPTURE_CHANGED;
+  }
+
+  bool IsTouchEvent() const {
+    return type_ == ET_TOUCH_RELEASED ||
+           type_ == ET_TOUCH_PRESSED ||
+           type_ == ET_TOUCH_MOVED ||
+           type_ == ET_TOUCH_CANCELLED;
+  }
+
+  bool IsGestureEvent() const {
+    switch (type_) {
+      case ET_GESTURE_SCROLL_BEGIN:
+      case ET_GESTURE_SCROLL_END:
+      case ET_GESTURE_SCROLL_UPDATE:
+      case ET_GESTURE_TAP:
+      case ET_GESTURE_TAP_CANCEL:
+      case ET_GESTURE_TAP_DOWN:
+      case ET_GESTURE_BEGIN:
+      case ET_GESTURE_END:
+      case ET_GESTURE_TWO_FINGER_TAP:
+      case ET_GESTURE_PINCH_BEGIN:
+      case ET_GESTURE_PINCH_END:
+      case ET_GESTURE_PINCH_UPDATE:
+      case ET_GESTURE_LONG_PRESS:
+      case ET_GESTURE_LONG_TAP:
+      case ET_GESTURE_SWIPE:
+      case ET_GESTURE_SHOW_PRESS:
+      case ET_GESTURE_WIN8_EDGE_SWIPE:
+        // When adding a gesture event which is paired with an event which
+        // occurs earlier, add the event to |IsEndingEvent|.
+        return true;
+
+      case ET_SCROLL_FLING_CANCEL:
+      case ET_SCROLL_FLING_START:
+        // These can be ScrollEvents too. EF_FROM_TOUCH determines if they're
+        // Gesture or Scroll events.
+        return (flags_ & EF_FROM_TOUCH) == EF_FROM_TOUCH;
+
+      default:
+        break;
+    }
+    return false;
+  }
+
+  // An ending event is paired with the event which started it. Setting capture
+  // should not prevent ending events from getting to their initial target.
+  bool IsEndingEvent() const {
+    switch(type_) {
+      case ui::ET_TOUCH_CANCELLED:
+      case ui::ET_GESTURE_TAP_CANCEL:
+      case ui::ET_GESTURE_END:
+      case ui::ET_GESTURE_SCROLL_END:
+      case ui::ET_GESTURE_PINCH_END:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  bool IsScrollEvent() const {
+    // Flings can be GestureEvents too. EF_FROM_TOUCH determins if they're
+    // Gesture or Scroll events.
+    return type_ == ET_SCROLL ||
+           ((type_ == ET_SCROLL_FLING_START ||
+           type_ == ET_SCROLL_FLING_CANCEL) &&
+           !(flags() & EF_FROM_TOUCH));
+  }
+
+  bool IsScrollGestureEvent() const {
+    return type_ == ET_GESTURE_SCROLL_BEGIN ||
+           type_ == ET_GESTURE_SCROLL_UPDATE ||
+           type_ == ET_GESTURE_SCROLL_END;
+  }
+
+  bool IsFlingScrollEvent() const {
+    return type_ == ET_SCROLL_FLING_CANCEL ||
+           type_ == ET_SCROLL_FLING_START;
+  }
+
+  bool IsMouseWheelEvent() const {
+    return type_ == ET_MOUSEWHEEL;
+  }
+
+  bool IsLocatedEvent() const {
+    return IsMouseEvent() || IsScrollEvent() || IsTouchEvent() ||
+           IsGestureEvent();
+  }
+
+  // Convenience methods to cast |this| to a GestureEvent. IsGestureEvent()
+  // must be true as a precondition to calling these methods.
+  GestureEvent* AsGestureEvent();
+  const GestureEvent* AsGestureEvent() const;
+
+  // Returns true if the event has a valid |native_event_|.
+  bool HasNativeEvent() const;
+
+  // Immediately stops the propagation of the event. This must be called only
+  // from an EventHandler during an event-dispatch. Any event handler that may
+  // be in the list will not receive the event after this is called.
+  // Note that StopPropagation() can be called only for cancelable events.
+  void StopPropagation();
+  bool stopped_propagation() const { return !!(result_ & ER_CONSUMED); }
+
+  // Marks the event as having been handled. A handled event does not reach the
+  // next event phase. For example, if an event is handled during the pre-target
+  // phase, then the event is dispatched to all pre-target handlers, but not to
+  // the target or post-target handlers.
+  // Note that SetHandled() can be called only for cancelable events.
+  void SetHandled();
+  bool handled() const { return result_ != ER_UNHANDLED; }
+
+ protected:
+  Event(EventType type, base::TimeDelta time_stamp, int flags);
+  Event(const base::NativeEvent& native_event, EventType type, int flags);
+  Event(const Event& copy);
+  void SetType(EventType type);
+  void set_delete_native_event(bool delete_native_event) {
+    delete_native_event_ = delete_native_event;
+  }
+  void set_cancelable(bool cancelable) { cancelable_ = cancelable; }
+
+  void set_time_stamp(const base::TimeDelta& time_stamp) {
+    time_stamp_ = time_stamp;
+  }
+
+  void set_name(const std::string& name) { name_ = name; }
+
+ private:
+  friend class EventTestApi;
+
+  EventType type_;
+  std::string name_;
+  base::TimeDelta time_stamp_;
+  LatencyInfo latency_;
+  int flags_;
+  base::NativeEvent native_event_;
+  bool delete_native_event_;
+  bool cancelable_;
+  EventTarget* target_;
+  EventPhase phase_;
+  EventResult result_;
+
+  // The device id the event came from, or ED_UNKNOWN_DEVICE if the information
+  // is not available.
+  int source_device_id_;
+};
+
+class EVENTS_EXPORT CancelModeEvent : public Event {
+ public:
+  CancelModeEvent();
+  virtual ~CancelModeEvent();
+};
+
+class EVENTS_EXPORT LocatedEvent : public Event {
+ public:
+  virtual ~LocatedEvent();
+
+  float x() const { return location_.x(); }
+  float y() const { return location_.y(); }
+  void set_location(const gfx::PointF& location) { location_ = location; }
+  // TODO(tdresser): Always return floating point location. See
+  // crbug.com/337824.
+  gfx::Point location() const { return gfx::ToFlooredPoint(location_); }
+  const gfx::PointF& location_f() const { return location_; }
+  void set_root_location(const gfx::PointF& root_location) {
+    root_location_ = root_location;
+  }
+  gfx::Point root_location() const {
+    return gfx::ToFlooredPoint(root_location_);
+  }
+  const gfx::PointF& root_location_f() const {
+    return root_location_;
+  }
+
+  // Transform the locations using |inverted_root_transform|.
+  // This is applied to both |location_| and |root_location_|.
+  virtual void UpdateForRootTransform(
+      const gfx::Transform& inverted_root_transform);
+
+  template <class T> void ConvertLocationToTarget(T* source, T* target) {
+    if (!target || target == source)
+      return;
+    // TODO(tdresser): Rewrite ConvertPointToTarget to use PointF. See
+    // crbug.com/337824.
+    gfx::Point offset = gfx::ToFlooredPoint(location_);
+    T::ConvertPointToTarget(source, target, &offset);
+    gfx::Vector2d diff = gfx::ToFlooredPoint(location_) - offset;
+    location_= location_ - diff;
+  }
+
+ protected:
+  friend class LocatedEventTestApi;
+  explicit LocatedEvent(const base::NativeEvent& native_event);
+
+  // Create a new LocatedEvent which is identical to the provided model.
+  // If source / target windows are provided, the model location will be
+  // converted from |source| coordinate system to |target| coordinate system.
+  template <class T>
+  LocatedEvent(const LocatedEvent& model, T* source, T* target)
+      : Event(model),
+        location_(model.location_),
+        root_location_(model.root_location_) {
+    ConvertLocationToTarget(source, target);
+  }
+
+  // Used for synthetic events in testing.
+  LocatedEvent(EventType type,
+               const gfx::PointF& location,
+               const gfx::PointF& root_location,
+               base::TimeDelta time_stamp,
+               int flags);
+
+  gfx::PointF location_;
+
+  // |location_| multiplied by an optional transformation matrix for
+  // rotations, animations and skews.
+  gfx::PointF root_location_;
+};
+
+class EVENTS_EXPORT MouseEvent : public LocatedEvent {
+ public:
+  explicit MouseEvent(const base::NativeEvent& native_event);
+
+  // Create a new MouseEvent based on the provided model.
+  // Uses the provided |type| and |flags| for the new event.
+  // If source / target windows are provided, the model location will be
+  // converted from |source| coordinate system to |target| coordinate system.
+  template <class T>
+  MouseEvent(const MouseEvent& model, T* source, T* target)
+      : LocatedEvent(model, source, target),
+        changed_button_flags_(model.changed_button_flags_) {
+  }
+
+  template <class T>
+  MouseEvent(const MouseEvent& model,
+             T* source,
+             T* target,
+             EventType type,
+             int flags)
+      : LocatedEvent(model, source, target),
+        changed_button_flags_(model.changed_button_flags_) {
+    SetType(type);
+    set_flags(flags);
+  }
+
+  // Used for synthetic events in testing and by the gesture recognizer.
+  MouseEvent(EventType type,
+             const gfx::PointF& location,
+             const gfx::PointF& root_location,
+             int flags,
+             int changed_button_flags);
+
+  // Conveniences to quickly test what button is down
+  bool IsOnlyLeftMouseButton() const {
+    return (flags() & EF_LEFT_MOUSE_BUTTON) &&
+      !(flags() & (EF_MIDDLE_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON));
+  }
+
+  bool IsLeftMouseButton() const {
+    return (flags() & EF_LEFT_MOUSE_BUTTON) != 0;
+  }
+
+  bool IsOnlyMiddleMouseButton() const {
+    return (flags() & EF_MIDDLE_MOUSE_BUTTON) &&
+      !(flags() & (EF_LEFT_MOUSE_BUTTON | EF_RIGHT_MOUSE_BUTTON));
+  }
+
+  bool IsMiddleMouseButton() const {
+    return (flags() & EF_MIDDLE_MOUSE_BUTTON) != 0;
+  }
+
+  bool IsOnlyRightMouseButton() const {
+    return (flags() & EF_RIGHT_MOUSE_BUTTON) &&
+      !(flags() & (EF_LEFT_MOUSE_BUTTON | EF_MIDDLE_MOUSE_BUTTON));
+  }
+
+  bool IsRightMouseButton() const {
+    return (flags() & EF_RIGHT_MOUSE_BUTTON) != 0;
+  }
+
+  bool IsAnyButton() const {
+    return (flags() & (EF_LEFT_MOUSE_BUTTON | EF_MIDDLE_MOUSE_BUTTON |
+                       EF_RIGHT_MOUSE_BUTTON)) != 0;
+  }
+
+  // Compares two mouse down events and returns true if the second one should
+  // be considered a repeat of the first.
+  static bool IsRepeatedClickEvent(
+      const MouseEvent& event1,
+      const MouseEvent& event2);
+
+  // Get the click count. Can be 1, 2 or 3 for mousedown messages, 0 otherwise.
+  int GetClickCount() const;
+
+  // Set the click count for a mousedown message. Can be 1, 2 or 3.
+  void SetClickCount(int click_count);
+
+  // Identifies the button that changed. During a press this corresponds to the
+  // button that was pressed and during a release this corresponds to the button
+  // that was released.
+  // NOTE: during a press and release flags() contains the complete set of
+  // flags. Use this to determine the button that was pressed or released.
+  int changed_button_flags() const { return changed_button_flags_; }
+
+  // Updates the button that changed.
+  void set_changed_button_flags(int flags) { changed_button_flags_ = flags; }
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(EventTest, DoubleClickRequiresRelease);
+  FRIEND_TEST_ALL_PREFIXES(EventTest, SingleClickRightLeft);
+
+  // Returns the repeat count based on the previous mouse click, if it is
+  // recent enough and within a small enough distance.
+  static int GetRepeatCount(const MouseEvent& click_event);
+
+  // Resets the last_click_event_ for unit tests.
+  static void ResetLastClickForTest();
+
+  // See description above getter for details.
+  int changed_button_flags_;
+
+  static MouseEvent* last_click_event_;
+
+  // We can create a MouseEvent for a native event more than once. We set this
+  // to true when the next event either has a different timestamp or we see a
+  // release signalling that the press (click) event was completed.
+  static bool last_click_complete_;
+};
+
+class ScrollEvent;
+
+class EVENTS_EXPORT MouseWheelEvent : public MouseEvent {
+ public:
+  // See |offset| for details.
+  static const int kWheelDelta;
+
+  explicit MouseWheelEvent(const base::NativeEvent& native_event);
+  explicit MouseWheelEvent(const ScrollEvent& scroll_event);
+  MouseWheelEvent(const MouseEvent& mouse_event, int x_offset, int y_offset);
+  MouseWheelEvent(const MouseWheelEvent& mouse_wheel_event);
+
+  template <class T>
+  MouseWheelEvent(const MouseWheelEvent& model,
+                  T* source,
+                  T* target)
+      : MouseEvent(model, source, target, model.type(), model.flags()),
+        offset_(model.x_offset(), model.y_offset()) {
+  }
+
+  // Used for synthetic events in testing and by the gesture recognizer.
+  MouseWheelEvent(const gfx::Vector2d& offset,
+                  const gfx::PointF& location,
+                  const gfx::PointF& root_location,
+                  int flags,
+                  int changed_button_flags);
+
+  // The amount to scroll. This is in multiples of kWheelDelta.
+  // Note: x_offset() > 0/y_offset() > 0 means scroll left/up.
+  int x_offset() const { return offset_.x(); }
+  int y_offset() const { return offset_.y(); }
+  const gfx::Vector2d& offset() const { return offset_; }
+
+  // Overridden from LocatedEvent.
+  virtual void UpdateForRootTransform(
+      const gfx::Transform& inverted_root_transform) OVERRIDE;
+
+ private:
+  gfx::Vector2d offset_;
+};
+
+class EVENTS_EXPORT TouchEvent : public LocatedEvent {
+ public:
+  explicit TouchEvent(const base::NativeEvent& native_event);
+
+  // Create a new TouchEvent which is identical to the provided model.
+  // If source / target windows are provided, the model location will be
+  // converted from |source| coordinate system to |target| coordinate system.
+  template <class T>
+  TouchEvent(const TouchEvent& model, T* source, T* target)
+      : LocatedEvent(model, source, target),
+        touch_id_(model.touch_id_),
+        radius_x_(model.radius_x_),
+        radius_y_(model.radius_y_),
+        rotation_angle_(model.rotation_angle_),
+        force_(model.force_) {
+  }
+
+  TouchEvent(EventType type,
+             const gfx::PointF& location,
+             int touch_id,
+             base::TimeDelta time_stamp);
+
+  TouchEvent(EventType type,
+             const gfx::PointF& location,
+             int flags,
+             int touch_id,
+             base::TimeDelta timestamp,
+             float radius_x,
+             float radius_y,
+             float angle,
+             float force);
+
+  virtual ~TouchEvent();
+
+  int touch_id() const { return touch_id_; }
+  float radius_x() const { return radius_x_; }
+  float radius_y() const { return radius_y_; }
+  float rotation_angle() const { return rotation_angle_; }
+  float force() const { return force_; }
+
+  // Used for unit tests.
+  void set_radius_x(const float r) { radius_x_ = r; }
+  void set_radius_y(const float r) { radius_y_ = r; }
+
+  // Overridden from LocatedEvent.
+  virtual void UpdateForRootTransform(
+      const gfx::Transform& inverted_root_transform) OVERRIDE;
+
+ protected:
+  void set_radius(float radius_x, float radius_y) {
+    radius_x_ = radius_x;
+    radius_y_ = radius_y;
+  }
+
+  void set_rotation_angle(float rotation_angle) {
+    rotation_angle_ = rotation_angle;
+  }
+
+  void set_force(float force) { force_ = force; }
+
+ private:
+  // The identity (typically finger) of the touch starting at 0 and incrementing
+  // for each separable additional touch that the hardware can detect.
+  const int touch_id_;
+
+  // Radius of the X (major) axis of the touch ellipse. 0.0 if unknown.
+  float radius_x_;
+
+  // Radius of the Y (minor) axis of the touch ellipse. 0.0 if unknown.
+  float radius_y_;
+
+  // Angle of the major axis away from the X axis. Default 0.0.
+  float rotation_angle_;
+
+  // Force (pressure) of the touch. Normalized to be [0, 1]. Default to be 0.0.
+  float force_;
+};
+
+// An interface that individual platforms can use to store additional data on
+// KeyEvent.
+//
+// Currently only used in mojo.
+class EVENTS_EXPORT ExtendedKeyEventData {
+ public:
+  virtual ~ExtendedKeyEventData() {}
+
+  virtual ExtendedKeyEventData* Clone() const = 0;
+};
+
+// A KeyEvent is really two distinct classes, melded together due to the
+// DOM legacy of Windows key events: a keystroke event (is_char_ == false),
+// or a character event (is_char_ == true).
+//
+// For a keystroke event,
+// -- is_char_ is false.
+// -- type() can be any one of ET_KEY_PRESSED, ET_KEY_RELEASED,
+//    ET_TRANSLATED_KEY_PRESS, or ET_TRANSLATED_KEY_RELEASE.
+// -- character_ functions as a bypass or cache for GetCharacter().
+// -- key_code_ is a VKEY_ value associated with the key. For printable
+//    characters, this may or may not be a mapped value, imitating MS Windows:
+//    if the mapped key generates a character that has an associated VKEY_
+//    code, then key_code_ is that code; if not, then key_code_ is the unmapped
+//    VKEY_ code. For example, US, Greek, Cyrillic, Japanese, etc. all use
+//    VKEY_Q for the key beside Tab, while French uses VKEY_A.
+// -- code_ is in one-to-one correspondence with a physical keyboard
+//    location, and does not vary depending on key layout.
+//
+// For a character event,
+// -- is_char_ is true.
+// -- type() is ET_KEY_PRESSED.
+// -- character_ is a UTF-16 character value.
+// -- key_code_ is conflated with character_ by some code, because both
+//    arrive in the wParam field of a Windows event.
+// -- code_ is the empty string.
+//
+class EVENTS_EXPORT KeyEvent : public Event {
+ public:
+  // Create a KeyEvent from a NativeEvent. For Windows this native event can
+  // be either a keystroke message (WM_KEYUP/WM_KEYDOWN) or a character message
+  // (WM_CHAR). Other systems have only keystroke events.
+  explicit KeyEvent(const base::NativeEvent& native_event);
+
+  // Create a keystroke event.
+  KeyEvent(EventType type, KeyboardCode key_code, int flags);
+
+  // Create a character event.
+  KeyEvent(base::char16 character, KeyboardCode key_code, int flags);
+
+  // Used for synthetic events with code of DOM KeyboardEvent (e.g. 'KeyA')
+  // See also: ui/events/keycodes/dom4/keycode_converter_data.h
+  KeyEvent(EventType type,
+           KeyboardCode key_code,
+           const std::string& code,
+           int flags);
+
+  KeyEvent(const KeyEvent& rhs);
+
+  KeyEvent& operator=(const KeyEvent& rhs);
+
+  virtual ~KeyEvent();
+
+  // TODO(erg): While we transition to mojo, we have to hack around a mismatch
+  // in our event types. Our ui::Events don't really have all the data we need
+  // to process key events, and we instead do per-platform conversions with
+  // native HWNDs or XEvents. And we can't reliably send those native data
+  // types across mojo types in a cross-platform way. So instead, we set the
+  // resulting data when read across IPC boundaries.
+  void SetExtendedKeyEventData(scoped_ptr<ExtendedKeyEventData> data);
+  const ExtendedKeyEventData* extended_key_event_data() const {
+    return extended_key_event_data_.get();
+  }
+
+  // This bypasses the normal mapping from keystroke events to characters,
+  // which allows an I18N virtual keyboard to fabricate a keyboard event that
+  // does not have a corresponding KeyboardCode (example: U+00E1 Latin small
+  // letter A with acute, U+0410 Cyrillic capital letter A).
+  void set_character(base::char16 character) { character_ = character; }
+
+  // Gets the character generated by this key event. It only supports Unicode
+  // BMP characters.
+  base::char16 GetCharacter() const;
+
+  // If this is a keystroke event with key_code_ VKEY_RETURN, returns '\r';
+  // otherwise returns the same as GetCharacter().
+  base::char16 GetUnmodifiedText() const;
+
+  // If the Control key is down in the event, returns a layout-independent
+  // character (corresponding to US layout); otherwise returns the same
+  // as GetUnmodifiedText().
+  base::char16 GetText() const;
+
+  // Gets the platform key code. For XKB, this is the xksym value.
+  void set_platform_keycode(uint32 keycode) { platform_keycode_ = keycode; }
+  uint32 platform_keycode() const { return platform_keycode_; }
+
+  // Gets the associated (Windows-based) KeyboardCode for this key event.
+  // Historically, this has also been used to obtain the character associated
+  // with a character event, because both use the Window message 'wParam' field.
+  // This should be avoided; if necessary for backwards compatibility, use
+  // GetConflatedWindowsKeyCode().
+  KeyboardCode key_code() const { return key_code_; }
+
+  // True if this is a character event, false if this is a keystroke event.
+  bool is_char() const { return is_char_; }
+
+  // This is only intended to be used externally by classes that are modifying
+  // events in an EventRewriter.
+  void set_key_code(KeyboardCode key_code) { key_code_ = key_code; }
+
+  // Returns the same value as key_code(), except that located codes are
+  // returned in place of non-located ones (e.g. VKEY_LSHIFT or VKEY_RSHIFT
+  // instead of VKEY_SHIFT). This is a hybrid of semantic and physical
+  // for legacy DOM reasons.
+  KeyboardCode GetLocatedWindowsKeyboardCode() const;
+
+  // For a keystroke event, returns the same value as key_code().
+  // For a character event, returns the same value as GetCharacter().
+  // This exists for backwards compatibility with Windows key events.
+  uint16 GetConflatedWindowsKeyCode() const;
+
+  // Returns true for [Alt]+<num-pad digit> Unicode alt key codes used by Win.
+  // TODO(msw): Additional work may be needed for analogues on other platforms.
+  bool IsUnicodeKeyCode() const;
+
+  std::string code() const { return code_; }
+
+  // Normalizes flags_ so that it describes the state after the event.
+  // (Native X11 event flags describe the state before the event.)
+  void NormalizeFlags();
+
+  // Returns true if the key event has already been processed by an input method
+  // and there is no need to pass the key event to the input method again.
+  bool IsTranslated() const;
+  // Marks this key event as translated or not translated.
+  void SetTranslated(bool translated);
+
+ protected:
+  friend class KeyEventTestApi;
+
+  // This allows a subclass TranslatedKeyEvent to be a non character event.
+  void set_is_char(bool is_char) { is_char_ = is_char; }
+
+ private:
+  // True if the key press originated from a 'right' key (VKEY_RSHIFT, etc.).
+  bool IsRightSideKey() const;
+
+  KeyboardCode key_code_;
+
+  // String of 'code' defined in DOM KeyboardEvent (e.g. 'KeyA', 'Space')
+  // http://www.w3.org/TR/uievents/#keyboard-key-codes.
+  //
+  // This value represents the physical position in the keyboard and can be
+  // converted from / to keyboard scan code like XKB.
+  std::string code_;
+
+  // True if this is a character event, false if this is a keystroke event.
+  bool is_char_;
+
+  // The platform related keycode value. For XKB, it's keysym value.
+  // For now, this is used for CharacterComposer in ChromeOS.
+  uint32 platform_keycode_;
+
+  // String of 'key' defined in DOM KeyboardEvent (e.g. 'a', 'â')
+  // http://www.w3.org/TR/uievents/#keyboard-key-codes.
+  //
+  // This value represents the text that the key event will insert to input
+  // field. For key with modifier key, it may have specifial text.
+  // e.g. CTRL+A has '\x01'.
+  mutable base::char16 character_;
+
+  // Parts of our event handling require raw native events (see both the
+  // windows and linux implementations of web_input_event in content/). Because
+  // mojo instead serializes and deserializes events in potentially different
+  // processes, we need to have a mechanism to keep track of this data.
+  scoped_ptr<ExtendedKeyEventData> extended_key_event_data_;
+
+  static bool IsRepeated(const KeyEvent& event);
+
+  static KeyEvent* last_key_event_;
+};
+
+class EVENTS_EXPORT ScrollEvent : public MouseEvent {
+ public:
+  explicit ScrollEvent(const base::NativeEvent& native_event);
+  template <class T>
+  ScrollEvent(const ScrollEvent& model,
+              T* source,
+              T* target)
+      : MouseEvent(model, source, target),
+        x_offset_(model.x_offset_),
+        y_offset_(model.y_offset_),
+        x_offset_ordinal_(model.x_offset_ordinal_),
+        y_offset_ordinal_(model.y_offset_ordinal_),
+        finger_count_(model.finger_count_){
+  }
+
+  // Used for tests.
+  ScrollEvent(EventType type,
+              const gfx::PointF& location,
+              base::TimeDelta time_stamp,
+              int flags,
+              float x_offset,
+              float y_offset,
+              float x_offset_ordinal,
+              float y_offset_ordinal,
+              int finger_count);
+
+  // Scale the scroll event's offset value.
+  // This is useful in the multi-monitor setup where it needs to be scaled
+  // to provide a consistent user experience.
+  void Scale(const float factor);
+
+  float x_offset() const { return x_offset_; }
+  float y_offset() const { return y_offset_; }
+  float x_offset_ordinal() const { return x_offset_ordinal_; }
+  float y_offset_ordinal() const { return y_offset_ordinal_; }
+  int finger_count() const { return finger_count_; }
+
+ private:
+  // Potential accelerated offsets.
+  float x_offset_;
+  float y_offset_;
+  // Unaccelerated offsets.
+  float x_offset_ordinal_;
+  float y_offset_ordinal_;
+  // Number of fingers on the pad.
+  int finger_count_;
+};
+
+class EVENTS_EXPORT GestureEvent : public LocatedEvent {
+ public:
+  GestureEvent(float x,
+               float y,
+               int flags,
+               base::TimeDelta time_stamp,
+               const GestureEventDetails& details);
+
+  // Create a new GestureEvent which is identical to the provided model.
+  // If source / target windows are provided, the model location will be
+  // converted from |source| coordinate system to |target| coordinate system.
+  template <typename T>
+  GestureEvent(const GestureEvent& model, T* source, T* target)
+      : LocatedEvent(model, source, target),
+        details_(model.details_) {
+  }
+
+  virtual ~GestureEvent();
+
+  const GestureEventDetails& details() const { return details_; }
+
+ private:
+  GestureEventDetails details_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_H_
diff --git a/ui/events/event_constants.h b/ui/events/event_constants.h
new file mode 100644
index 0000000..c3677ad
--- /dev/null
+++ b/ui/events/event_constants.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2012 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 UI_EVENTS_EVENT_CONSTANTS_H_
+#define UI_EVENTS_EVENT_CONSTANTS_H_
+
+namespace ui {
+
+// Event types. (prefixed because of a conflict with windows headers)
+enum EventType {
+  ET_UNKNOWN = 0,
+  ET_MOUSE_PRESSED,
+  ET_MOUSE_DRAGGED,
+  ET_MOUSE_RELEASED,
+  ET_MOUSE_MOVED,
+  ET_MOUSE_ENTERED,
+  ET_MOUSE_EXITED,
+  ET_KEY_PRESSED,
+  ET_KEY_RELEASED,
+  ET_MOUSEWHEEL,
+  ET_MOUSE_CAPTURE_CHANGED,  // Event has no location.
+  ET_TOUCH_RELEASED,
+  ET_TOUCH_PRESSED,
+  ET_TOUCH_MOVED,
+  ET_TOUCH_CANCELLED,
+  ET_DROP_TARGET_EVENT,
+  ET_TRANSLATED_KEY_PRESS,
+  ET_TRANSLATED_KEY_RELEASE,
+
+  // GestureEvent types
+  ET_GESTURE_SCROLL_BEGIN,
+  ET_GESTURE_TYPE_START = ET_GESTURE_SCROLL_BEGIN,
+  ET_GESTURE_SCROLL_END,
+  ET_GESTURE_SCROLL_UPDATE,
+  ET_GESTURE_TAP,
+  ET_GESTURE_TAP_DOWN,
+  ET_GESTURE_TAP_CANCEL,
+  ET_GESTURE_TAP_UNCONFIRMED, // User tapped, but the tap delay hasn't expired.
+  ET_GESTURE_DOUBLE_TAP,
+  ET_GESTURE_BEGIN,  // The first event sent when each finger is pressed.
+  ET_GESTURE_END,    // Sent for each released finger.
+  ET_GESTURE_TWO_FINGER_TAP,
+  ET_GESTURE_PINCH_BEGIN,
+  ET_GESTURE_PINCH_END,
+  ET_GESTURE_PINCH_UPDATE,
+  ET_GESTURE_LONG_PRESS,
+  ET_GESTURE_LONG_TAP,
+  // A SWIPE gesture can happen at the end of a touch sequence involving one or
+  // more fingers if the finger velocity was high enough when the first finger
+  // was released.
+  ET_GESTURE_SWIPE,
+  ET_GESTURE_SHOW_PRESS,
+
+  // Sent by Win8+ metro when the user swipes from the bottom or top.
+  ET_GESTURE_WIN8_EDGE_SWIPE,
+
+  // Scroll support.
+  // TODO[davemoore] we need to unify these events w/ touch and gestures.
+  ET_SCROLL,
+  ET_SCROLL_FLING_START,
+  ET_SCROLL_FLING_CANCEL,
+  ET_GESTURE_TYPE_END = ET_SCROLL_FLING_CANCEL,
+
+  // Sent by the system to indicate any modal type operations, such as drag and
+  // drop or menus, should stop.
+  ET_CANCEL_MODE,
+
+  // Sent by the CrOS gesture library for interesting patterns that we want
+  // to track with the UMA system.
+  ET_UMA_DATA,
+
+  // Must always be last. User namespace starts above this value.
+  // See ui::RegisterCustomEventType().
+  ET_LAST
+};
+
+// Event flags currently supported
+enum EventFlags {
+  EF_NONE                = 0,       // Used to denote no flags explicitly
+  EF_CAPS_LOCK_DOWN      = 1 << 0,
+  EF_SHIFT_DOWN          = 1 << 1,
+  EF_CONTROL_DOWN        = 1 << 2,
+  EF_ALT_DOWN            = 1 << 3,
+  EF_LEFT_MOUSE_BUTTON   = 1 << 4,
+  EF_MIDDLE_MOUSE_BUTTON = 1 << 5,
+  EF_RIGHT_MOUSE_BUTTON  = 1 << 6,
+  EF_COMMAND_DOWN        = 1 << 7,  // GUI Key (e.g. Command on OS X keyboards,
+                                    // Search on Chromebook keyboards,
+                                    // Windows on MS-oriented keyboards)
+  EF_EXTENDED            = 1 << 8,  // Windows extended key (see WM_KEYDOWN doc)
+  EF_IS_SYNTHESIZED      = 1 << 9,
+  EF_ALTGR_DOWN          = 1 << 10,
+  EF_MOD3_DOWN           = 1 << 11,
+};
+
+// Flags specific to key events
+enum KeyEventFlags {
+  EF_NUMPAD_KEY         = 1 << 16,  // Key originates from number pad (Xkb only)
+  EF_IME_FABRICATED_KEY = 1 << 17,  // Key event fabricated by the underlying
+                                    // IME without a user action.
+                                    // (Linux X11 only)
+  EF_IS_REPEAT          = 1 << 18,
+  EF_FUNCTION_KEY       = 1 << 19,  // Key originates from function key row
+  EF_FINAL              = 1 << 20,  // Do not remap; the event was created with
+                                    // the desired final values.
+};
+
+// Flags specific to mouse events
+enum MouseEventFlags {
+  EF_IS_DOUBLE_CLICK     = 1 << 16,
+  EF_IS_TRIPLE_CLICK     = 1 << 17,
+  EF_IS_NON_CLIENT       = 1 << 18,
+  EF_FROM_TOUCH          = 1 << 19,  // Indicates this mouse event is generated
+                                     // from an unconsumed touch/gesture event.
+  EF_TOUCH_ACCESSIBILITY = 1 << 20,  // Indicates this event was generated from
+                                     // touch accessibility mode.
+};
+
+// Result of dispatching an event.
+enum EventResult {
+  ER_UNHANDLED = 0,       // The event hasn't been handled. The event can be
+                          // propagated to other handlers.
+  ER_HANDLED   = 1 << 0,  // The event has already been handled, but it can
+                          // still be propagated to other handlers.
+  ER_CONSUMED  = 1 << 1,  // The event has been handled, and it should not be
+                          // propagated to other handlers.
+};
+
+// Phase of the event dispatch.
+enum EventPhase {
+  EP_PREDISPATCH,
+  EP_PRETARGET,
+  EP_TARGET,
+  EP_POSTTARGET,
+  EP_POSTDISPATCH
+};
+
+// Device ID for Touch and Key Events.
+enum EventDeviceId {
+  ED_UNKNOWN_DEVICE = -1
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_CONSTANTS_H_
diff --git a/ui/events/event_dispatcher.cc b/ui/events/event_dispatcher.cc
new file mode 100644
index 0000000..ca24cde
--- /dev/null
+++ b/ui/events/event_dispatcher.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 2012 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 "ui/events/event_dispatcher.h"
+
+#include <algorithm>
+
+#include "ui/events/event_target.h"
+#include "ui/events/event_targeter.h"
+
+namespace ui {
+
+namespace {
+
+class ScopedDispatchHelper : public Event::DispatcherApi {
+ public:
+  explicit ScopedDispatchHelper(Event* event)
+      : Event::DispatcherApi(event) {
+    set_result(ui::ER_UNHANDLED);
+  }
+
+  virtual ~ScopedDispatchHelper() {
+    set_phase(EP_POSTDISPATCH);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedDispatchHelper);
+};
+
+}  // namespace
+
+EventDispatcherDelegate::EventDispatcherDelegate()
+    : dispatcher_(NULL) {
+}
+
+EventDispatcherDelegate::~EventDispatcherDelegate() {
+  if (dispatcher_)
+    dispatcher_->OnDispatcherDelegateDestroyed();
+}
+
+Event* EventDispatcherDelegate::current_event() {
+  return dispatcher_ ? dispatcher_->current_event() : NULL;
+}
+
+EventDispatchDetails EventDispatcherDelegate::DispatchEvent(EventTarget* target,
+                                                            Event* event) {
+  CHECK(target);
+  Event::DispatcherApi dispatch_helper(event);
+  dispatch_helper.set_phase(EP_PREDISPATCH);
+  dispatch_helper.set_result(ER_UNHANDLED);
+
+  EventDispatchDetails details = PreDispatchEvent(target, event);
+  if (!event->handled() &&
+      !details.dispatcher_destroyed &&
+      !details.target_destroyed) {
+    details = DispatchEventToTarget(target, event);
+  }
+  bool target_destroyed_during_dispatch = details.target_destroyed;
+  if (!details.dispatcher_destroyed) {
+    details = PostDispatchEvent(target_destroyed_during_dispatch ?
+        NULL : target, *event);
+  }
+
+  details.target_destroyed |= target_destroyed_during_dispatch;
+  return details;
+}
+
+EventDispatchDetails EventDispatcherDelegate::PreDispatchEvent(
+    EventTarget* target, Event* event) {
+  return EventDispatchDetails();
+}
+
+EventDispatchDetails EventDispatcherDelegate::PostDispatchEvent(
+    EventTarget* target, const Event& event) {
+  return EventDispatchDetails();
+}
+
+EventDispatchDetails EventDispatcherDelegate::DispatchEventToTarget(
+    EventTarget* target,
+    Event* event) {
+  EventDispatcher* old_dispatcher = dispatcher_;
+  EventDispatcher dispatcher(this);
+  dispatcher_ = &dispatcher;
+  dispatcher.ProcessEvent(target, event);
+  if (!dispatcher.delegate_destroyed())
+    dispatcher_ = old_dispatcher;
+  else if (old_dispatcher)
+    old_dispatcher->OnDispatcherDelegateDestroyed();
+
+  EventDispatchDetails details;
+  details.dispatcher_destroyed = dispatcher.delegate_destroyed();
+  details.target_destroyed =
+      (!details.dispatcher_destroyed && !CanDispatchToTarget(target));
+  return details;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventDispatcher:
+
+EventDispatcher::EventDispatcher(EventDispatcherDelegate* delegate)
+    : delegate_(delegate),
+      current_event_(NULL) {
+}
+
+EventDispatcher::~EventDispatcher() {
+}
+
+void EventDispatcher::OnHandlerDestroyed(EventHandler* handler) {
+  handler_list_.erase(std::find(handler_list_.begin(),
+                                handler_list_.end(),
+                                handler));
+}
+
+void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) {
+  if (!target || !target->CanAcceptEvent(*event))
+    return;
+
+  ScopedDispatchHelper dispatch_helper(event);
+  dispatch_helper.set_target(target);
+
+  handler_list_.clear();
+  target->GetPreTargetHandlers(&handler_list_);
+
+  dispatch_helper.set_phase(EP_PRETARGET);
+  DispatchEventToEventHandlers(&handler_list_, event);
+  if (event->handled())
+    return;
+
+  // If the event hasn't been consumed, trigger the default handler. Note that
+  // even if the event has already been handled (i.e. return result has
+  // ER_HANDLED set), that means that the event should still be processed at
+  // this layer, however it should not be processed in the next layer of
+  // abstraction.
+  if (delegate_ && delegate_->CanDispatchToTarget(target)) {
+    dispatch_helper.set_phase(EP_TARGET);
+    DispatchEvent(target, event);
+    if (event->handled())
+      return;
+  }
+
+  if (!delegate_ || !delegate_->CanDispatchToTarget(target))
+    return;
+
+  handler_list_.clear();
+  target->GetPostTargetHandlers(&handler_list_);
+  dispatch_helper.set_phase(EP_POSTTARGET);
+  DispatchEventToEventHandlers(&handler_list_, event);
+}
+
+void EventDispatcher::OnDispatcherDelegateDestroyed() {
+  delegate_ = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// EventDispatcher, private:
+
+void EventDispatcher::DispatchEventToEventHandlers(EventHandlerList* list,
+                                                   Event* event) {
+  for (EventHandlerList::const_iterator it = list->begin(),
+           end = list->end(); it != end; ++it) {
+    (*it)->dispatchers_.push(this);
+  }
+
+  while (!list->empty()) {
+    EventHandler* handler = (*list->begin());
+    if (delegate_ && !event->stopped_propagation())
+      DispatchEvent(handler, event);
+
+    if (!list->empty() && *list->begin() == handler) {
+      // The handler has not been destroyed (because if it were, then it would
+      // have been removed from the list).
+      CHECK(handler->dispatchers_.top() == this);
+      handler->dispatchers_.pop();
+      list->erase(list->begin());
+    }
+  }
+}
+
+void EventDispatcher::DispatchEvent(EventHandler* handler, Event* event) {
+  // If the target has been invalidated or deleted, don't dispatch the event.
+  if (!delegate_->CanDispatchToTarget(event->target())) {
+    if (event->cancelable())
+      event->StopPropagation();
+    return;
+  }
+
+  base::AutoReset<Event*> event_reset(&current_event_, event);
+  handler->OnEvent(event);
+  if (!delegate_ && event->cancelable())
+    event->StopPropagation();
+}
+
+}  // namespace ui
diff --git a/ui/events/event_dispatcher.h b/ui/events/event_dispatcher.h
new file mode 100644
index 0000000..a8c985e
--- /dev/null
+++ b/ui/events/event_dispatcher.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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 UI_EVENTS_EVENT_DISPATCHER_H_
+#define UI_EVENTS_EVENT_DISPATCHER_H_
+
+#include "base/auto_reset.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_handler.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class EventDispatcher;
+class EventTarget;
+class EventTargeter;
+
+struct EventDispatchDetails {
+  EventDispatchDetails()
+      : dispatcher_destroyed(false),
+        target_destroyed(false) {}
+  bool dispatcher_destroyed;
+  bool target_destroyed;
+};
+
+class EVENTS_EXPORT EventDispatcherDelegate {
+ public:
+  EventDispatcherDelegate();
+  virtual ~EventDispatcherDelegate();
+
+  // Returns whether an event can still be dispatched to a target. (e.g. during
+  // event dispatch, one of the handlers may have destroyed the target, in which
+  // case the event can no longer be dispatched to the target).
+  virtual bool CanDispatchToTarget(EventTarget* target) = 0;
+
+  // Returns the event being dispatched (or NULL if no event is being
+  // dispatched).
+  Event* current_event();
+
+  // Dispatches |event| to |target|. This calls |PreDispatchEvent()| before
+  // dispatching the event, and |PostDispatchEvent()| after the event has been
+  // dispatched.
+  EventDispatchDetails DispatchEvent(EventTarget* target, Event* event)
+      WARN_UNUSED_RESULT;
+
+ protected:
+  // This is called once a target has been determined for an event, right before
+  // the event is dispatched to the target. This function may modify |event| to
+  // prepare it for dispatch (e.g. update event flags, location etc.).
+  virtual EventDispatchDetails PreDispatchEvent(
+      EventTarget* target,
+      Event* event) WARN_UNUSED_RESULT;
+
+  // This is called right after the event dispatch is completed.
+  // |target| is NULL if the target was deleted during dispatch.
+  virtual EventDispatchDetails PostDispatchEvent(
+      EventTarget* target,
+      const Event& event) WARN_UNUSED_RESULT;
+
+ private:
+  // Dispatches the event to the target.
+  EventDispatchDetails DispatchEventToTarget(EventTarget* target,
+                                             Event* event) WARN_UNUSED_RESULT;
+
+  EventDispatcher* dispatcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventDispatcherDelegate);
+};
+
+// Dispatches events to appropriate targets.
+class EVENTS_EXPORT EventDispatcher {
+ public:
+  explicit EventDispatcher(EventDispatcherDelegate* delegate);
+  virtual ~EventDispatcher();
+
+  void ProcessEvent(EventTarget* target, Event* event);
+
+  const Event* current_event() const { return current_event_; }
+  Event* current_event() { return current_event_; }
+
+  bool delegate_destroyed() const { return !delegate_; }
+
+  void OnHandlerDestroyed(EventHandler* handler);
+  void OnDispatcherDelegateDestroyed();
+
+ private:
+  void DispatchEventToEventHandlers(EventHandlerList* list, Event* event);
+
+  // Dispatches an event, and makes sure it sets ER_CONSUMED on the
+  // event-handling result if the dispatcher itself has been destroyed during
+  // dispatching the event to the event handler.
+  void DispatchEvent(EventHandler* handler, Event* event);
+
+  EventDispatcherDelegate* delegate_;
+
+  Event* current_event_;
+
+  EventHandlerList handler_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventDispatcher);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_DISPATCHER_H_
diff --git a/ui/events/event_dispatcher_unittest.cc b/ui/events/event_dispatcher_unittest.cc
new file mode 100644
index 0000000..248784a
--- /dev/null
+++ b/ui/events/event_dispatcher_unittest.cc
@@ -0,0 +1,609 @@
+// Copyright (c) 2012 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 "ui/events/event_dispatcher.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/event_target.h"
+#include "ui/events/event_target_iterator.h"
+#include "ui/events/event_utils.h"
+
+namespace ui {
+
+namespace {
+
+class TestTarget : public EventTarget {
+ public:
+  TestTarget() : parent_(NULL), valid_(true) {}
+  virtual ~TestTarget() {}
+
+  void set_parent(TestTarget* parent) { parent_ = parent; }
+
+  bool valid() const { return valid_; }
+  void set_valid(bool valid) { valid_ = valid; }
+
+  void AddHandlerId(int id) {
+    handler_list_.push_back(id);
+  }
+
+  const std::vector<int>& handler_list() const { return handler_list_; }
+
+  void Reset() {
+    handler_list_.clear();
+    valid_ = true;
+  }
+
+ private:
+  // Overridden from EventTarget:
+  virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE {
+    return true;
+  }
+
+  virtual EventTarget* GetParentTarget() OVERRIDE {
+    return parent_;
+  }
+
+  virtual scoped_ptr<EventTargetIterator> GetChildIterator() const OVERRIDE {
+    return scoped_ptr<EventTargetIterator>();
+  }
+
+  virtual EventTargeter* GetEventTargeter() OVERRIDE {
+    return NULL;
+  }
+
+  TestTarget* parent_;
+  std::vector<int> handler_list_;
+  bool valid_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestTarget);
+};
+
+class TestEventHandler : public EventHandler {
+ public:
+  TestEventHandler(int id)
+      : id_(id),
+        event_result_(ER_UNHANDLED),
+        expect_pre_target_(false),
+        expect_post_target_(false),
+        received_pre_target_(false) {
+  }
+
+  virtual ~TestEventHandler() {}
+
+  virtual void ReceivedEvent(Event* event) {
+    static_cast<TestTarget*>(event->target())->AddHandlerId(id_);
+    if (event->phase() == ui::EP_POSTTARGET) {
+      EXPECT_TRUE(expect_post_target_);
+      if (expect_pre_target_)
+        EXPECT_TRUE(received_pre_target_);
+    } else if (event->phase() == ui::EP_PRETARGET) {
+      EXPECT_TRUE(expect_pre_target_);
+      received_pre_target_ = true;
+    } else {
+      NOTREACHED();
+    }
+  }
+
+  void set_event_result(EventResult result) { event_result_ = result; }
+
+  void set_expect_pre_target(bool expect) { expect_pre_target_ = expect; }
+  void set_expect_post_target(bool expect) { expect_post_target_ = expect; }
+
+ private:
+  // Overridden from EventHandler:
+  virtual void OnEvent(Event* event) OVERRIDE {
+    ui::EventHandler::OnEvent(event);
+    ReceivedEvent(event);
+    SetStatusOnEvent(event);
+  }
+
+  void SetStatusOnEvent(Event* event) {
+    if (event_result_ & ui::ER_CONSUMED)
+      event->StopPropagation();
+    if (event_result_ & ui::ER_HANDLED)
+      event->SetHandled();
+  }
+
+  int id_;
+  EventResult event_result_;
+  bool expect_pre_target_;
+  bool expect_post_target_;
+  bool received_pre_target_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
+};
+
+class NonCancelableEvent : public Event {
+ public:
+  NonCancelableEvent()
+      : Event(ui::ET_CANCEL_MODE, ui::EventTimeForNow(), 0) {
+    set_cancelable(false);
+  }
+
+  virtual ~NonCancelableEvent() {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NonCancelableEvent);
+};
+
+// Destroys the dispatcher-delegate when it receives any event.
+class EventHandlerDestroyDispatcherDelegate : public TestEventHandler {
+ public:
+  EventHandlerDestroyDispatcherDelegate(EventDispatcherDelegate* delegate,
+                                        int id)
+      : TestEventHandler(id),
+        dispatcher_delegate_(delegate) {
+  }
+
+  virtual ~EventHandlerDestroyDispatcherDelegate() {}
+
+ private:
+  virtual void ReceivedEvent(Event* event) OVERRIDE {
+    TestEventHandler::ReceivedEvent(event);
+    delete dispatcher_delegate_;
+  }
+
+  EventDispatcherDelegate* dispatcher_delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventHandlerDestroyDispatcherDelegate);
+};
+
+// Invalidates the target when it receives any event.
+class InvalidateTargetEventHandler : public TestEventHandler {
+ public:
+  explicit InvalidateTargetEventHandler(int id) : TestEventHandler(id) {}
+  virtual ~InvalidateTargetEventHandler() {}
+
+ private:
+  virtual void ReceivedEvent(Event* event) OVERRIDE {
+   TestEventHandler::ReceivedEvent(event);
+   TestTarget* target = static_cast<TestTarget*>(event->target());
+   target->set_valid(false);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(InvalidateTargetEventHandler);
+};
+
+// Destroys a second event handler when this handler gets an event.
+// Optionally also destroys the dispatcher.
+class EventHandlerDestroyer : public TestEventHandler {
+ public:
+  EventHandlerDestroyer(int id, EventHandler* destroy)
+      : TestEventHandler(id),
+        to_destroy_(destroy),
+        dispatcher_delegate_(NULL) {
+  }
+
+  virtual ~EventHandlerDestroyer() {
+    CHECK(!to_destroy_);
+  }
+
+  void set_dispatcher_delegate(EventDispatcherDelegate* dispatcher_delegate) {
+    dispatcher_delegate_ = dispatcher_delegate;
+  }
+
+ private:
+  virtual void ReceivedEvent(Event* event) OVERRIDE {
+    TestEventHandler::ReceivedEvent(event);
+    delete to_destroy_;
+    to_destroy_ = NULL;
+
+    if (dispatcher_delegate_) {
+      delete dispatcher_delegate_;
+      dispatcher_delegate_ = NULL;
+    }
+  }
+
+  EventHandler* to_destroy_;
+  EventDispatcherDelegate* dispatcher_delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventHandlerDestroyer);
+};
+
+class TestEventDispatcher : public EventDispatcherDelegate {
+ public:
+  TestEventDispatcher() {}
+
+  virtual ~TestEventDispatcher() {}
+
+  EventDispatchDetails ProcessEvent(EventTarget* target, Event* event) {
+    return DispatchEvent(target, event);
+  }
+
+ private:
+  // Overridden from EventDispatcherDelegate:
+  virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE {
+    TestTarget* test_target = static_cast<TestTarget*>(target);
+    return test_target->valid();
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(TestEventDispatcher);
+};
+
+}  // namespace
+
+TEST(EventDispatcherTest, EventDispatchOrder) {
+  TestEventDispatcher dispatcher;
+  TestTarget parent, child;
+  TestEventHandler h1(1), h2(2), h3(3), h4(4);
+  TestEventHandler h5(5), h6(6), h7(7), h8(8);
+
+  child.set_parent(&parent);
+
+  parent.AddPreTargetHandler(&h1);
+  parent.AddPreTargetHandler(&h2);
+
+  child.AddPreTargetHandler(&h3);
+  child.AddPreTargetHandler(&h4);
+
+  h1.set_expect_pre_target(true);
+  h2.set_expect_pre_target(true);
+  h3.set_expect_pre_target(true);
+  h4.set_expect_pre_target(true);
+
+  child.AddPostTargetHandler(&h5);
+  child.AddPostTargetHandler(&h6);
+
+  parent.AddPostTargetHandler(&h7);
+  parent.AddPostTargetHandler(&h8);
+
+  h5.set_expect_post_target(true);
+  h6.set_expect_post_target(true);
+  h7.set_expect_post_target(true);
+  h8.set_expect_post_target(true);
+
+  MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
+                   gfx::Point(3, 4), 0, 0);
+  Event::DispatcherApi event_mod(&mouse);
+  dispatcher.ProcessEvent(&child, &mouse);
+  EXPECT_FALSE(mouse.stopped_propagation());
+  EXPECT_FALSE(mouse.handled());
+
+  {
+    int expected[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
+    EXPECT_EQ(
+        std::vector<int>(expected, expected + sizeof(expected) / sizeof(int)),
+        child.handler_list());
+  }
+
+  child.Reset();
+  event_mod.set_phase(EP_PREDISPATCH);
+  event_mod.set_result(ER_UNHANDLED);
+
+  h1.set_event_result(ER_HANDLED);
+  dispatcher.ProcessEvent(&child, &mouse);
+  EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
+  EXPECT_FALSE(mouse.stopped_propagation());
+  EXPECT_TRUE(mouse.handled());
+  {
+    // |h1| marks the event as handled. So only the pre-target handlers should
+    // receive the event.
+    int expected[] = { 1, 2, 3, 4 };
+    EXPECT_EQ(
+        std::vector<int>(expected, expected + sizeof(expected) / sizeof(int)),
+        child.handler_list());
+  }
+
+  child.Reset();
+  event_mod.set_phase(EP_PREDISPATCH);
+  event_mod.set_result(ER_UNHANDLED);
+
+  int nexpected[] = { 1, 2, 3, 4, 5 };
+  h1.set_event_result(ER_UNHANDLED);
+  h5.set_event_result(ER_CONSUMED);
+  dispatcher.ProcessEvent(&child, &mouse);
+  EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
+  EXPECT_TRUE(mouse.stopped_propagation());
+  EXPECT_TRUE(mouse.handled());
+  EXPECT_EQ(
+      std::vector<int>(nexpected, nexpected + sizeof(nexpected) / sizeof(int)),
+      child.handler_list());
+
+  child.Reset();
+  event_mod.set_phase(EP_PREDISPATCH);
+  event_mod.set_result(ER_UNHANDLED);
+
+  int exp[] = { 1 };
+  h1.set_event_result(ER_CONSUMED);
+  dispatcher.ProcessEvent(&child, &mouse);
+  EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
+  EXPECT_TRUE(mouse.stopped_propagation());
+  EXPECT_TRUE(mouse.handled());
+  EXPECT_EQ(
+      std::vector<int>(exp, exp + sizeof(exp) / sizeof(int)),
+      child.handler_list());
+}
+
+// Tests that the event-phases are correct.
+TEST(EventDispatcherTest, EventDispatchPhase) {
+  TestEventDispatcher dispatcher;
+  TestTarget target;
+
+  TestEventHandler handler(11);
+
+  target.AddPreTargetHandler(&handler);
+  target.AddPostTargetHandler(&handler);
+  handler.set_expect_pre_target(true);
+  handler.set_expect_post_target(true);
+
+  MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
+                   gfx::Point(3, 4), 0, 0);
+  Event::DispatcherApi event_mod(&mouse);
+  dispatcher.ProcessEvent(&target, &mouse);
+  EXPECT_EQ(ER_UNHANDLED, mouse.result());
+
+  int handlers[] = { 11, 11 };
+  EXPECT_EQ(
+      std::vector<int>(handlers, handlers + sizeof(handlers) / sizeof(int)),
+      target.handler_list());
+}
+
+// Tests that if the dispatcher is destroyed in the middle of pre or post-target
+// dispatching events, it doesn't cause a crash.
+TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
+  // Test for pre-target first.
+  {
+    TestEventDispatcher* dispatcher = new TestEventDispatcher();
+    TestTarget target;
+    EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+    TestEventHandler h1(1), h2(2);
+
+    target.AddPreTargetHandler(&h1);
+    target.AddPreTargetHandler(&handler);
+    target.AddPreTargetHandler(&h2);
+
+    h1.set_expect_pre_target(true);
+    handler.set_expect_pre_target(true);
+    // |h2| should not receive any events at all since |handler| will have
+    // destroyed the dispatcher.
+    h2.set_expect_pre_target(false);
+
+    MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
+                     gfx::Point(3, 4), 0, 0);
+    EventDispatchDetails details = dispatcher->ProcessEvent(&target, &mouse);
+    EXPECT_TRUE(details.dispatcher_destroyed);
+    EXPECT_EQ(ER_CONSUMED, mouse.result());
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(5, target.handler_list()[1]);
+  }
+
+  // Test for non-cancelable event.
+  {
+    TestEventDispatcher* dispatcher = new TestEventDispatcher();
+    TestTarget target;
+    EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+    TestEventHandler h1(1), h2(2);
+
+    target.AddPreTargetHandler(&h1);
+    target.AddPreTargetHandler(&handler);
+    target.AddPreTargetHandler(&h2);
+
+    h1.set_expect_pre_target(true);
+    handler.set_expect_pre_target(true);
+    // |h2| should not receive any events at all since |handler| will have
+    // destroyed the dispatcher.
+    h2.set_expect_pre_target(false);
+
+    NonCancelableEvent event;
+    EventDispatchDetails details = dispatcher->ProcessEvent(&target, &event);
+    EXPECT_TRUE(details.dispatcher_destroyed);
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(5, target.handler_list()[1]);
+  }
+
+  // Now test for post-target.
+  {
+    TestEventDispatcher* dispatcher = new TestEventDispatcher();
+    TestTarget target;
+    EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+    TestEventHandler h1(1), h2(2);
+
+    target.AddPostTargetHandler(&h1);
+    target.AddPostTargetHandler(&handler);
+    target.AddPostTargetHandler(&h2);
+
+    h1.set_expect_post_target(true);
+    handler.set_expect_post_target(true);
+    // |h2| should not receive any events at all since |handler| will have
+    // destroyed the dispatcher.
+    h2.set_expect_post_target(false);
+
+    MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
+                     gfx::Point(3, 4), 0, 0);
+    EventDispatchDetails details = dispatcher->ProcessEvent(&target, &mouse);
+    EXPECT_TRUE(details.dispatcher_destroyed);
+    EXPECT_EQ(ER_CONSUMED, mouse.result());
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(5, target.handler_list()[1]);
+  }
+
+  // Test for non-cancelable event.
+  {
+    TestEventDispatcher* dispatcher = new TestEventDispatcher();
+    TestTarget target;
+    EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
+    TestEventHandler h1(1), h2(2);
+
+    target.AddPostTargetHandler(&h1);
+    target.AddPostTargetHandler(&handler);
+    target.AddPostTargetHandler(&h2);
+
+    h1.set_expect_post_target(true);
+    handler.set_expect_post_target(true);
+    // |h2| should not receive any events at all since |handler| will have
+    // destroyed the dispatcher.
+    h2.set_expect_post_target(false);
+
+    NonCancelableEvent event;
+    EventDispatchDetails details = dispatcher->ProcessEvent(&target, &event);
+    EXPECT_TRUE(details.dispatcher_destroyed);
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(5, target.handler_list()[1]);
+  }
+}
+
+// Tests that a target becoming invalid in the middle of pre- or post-target
+// event processing aborts processing.
+TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) {
+  TestEventDispatcher dispatcher;
+  TestTarget target;
+  TestEventHandler h1(1);
+  InvalidateTargetEventHandler invalidate_handler(2);
+  TestEventHandler h3(3);
+
+  target.AddPreTargetHandler(&h1);
+  target.AddPreTargetHandler(&invalidate_handler);
+  target.AddPreTargetHandler(&h3);
+
+  h1.set_expect_pre_target(true);
+  invalidate_handler.set_expect_pre_target(true);
+  // |h3| should not receive events as the target will be invalidated.
+  h3.set_expect_pre_target(false);
+
+  MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0,
+                   0);
+  EventDispatchDetails details = dispatcher.ProcessEvent(&target, &mouse);
+  EXPECT_FALSE(details.dispatcher_destroyed);
+  EXPECT_TRUE(details.target_destroyed);
+  EXPECT_FALSE(target.valid());
+  EXPECT_TRUE(mouse.stopped_propagation());
+  EXPECT_EQ(2U, target.handler_list().size());
+  EXPECT_EQ(1, target.handler_list()[0]);
+  EXPECT_EQ(2, target.handler_list()[1]);
+
+  // Test for non-cancelable event.
+  target.Reset();
+  NonCancelableEvent event;
+  details = dispatcher.ProcessEvent(&target, &event);
+  EXPECT_FALSE(details.dispatcher_destroyed);
+  EXPECT_TRUE(details.target_destroyed);
+  EXPECT_FALSE(target.valid());
+  EXPECT_EQ(2U, target.handler_list().size());
+  EXPECT_EQ(1, target.handler_list()[0]);
+  EXPECT_EQ(2, target.handler_list()[1]);
+}
+
+// Tests that if an event-handler gets destroyed during event-dispatch, it does
+// not cause a crash.
+TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) {
+  {
+    TestEventDispatcher dispatcher;
+    TestTarget target;
+    TestEventHandler h1(1);
+    TestEventHandler* h3 = new TestEventHandler(3);
+    EventHandlerDestroyer handle_destroyer(2, h3);
+
+    target.AddPreTargetHandler(&h1);
+    target.AddPreTargetHandler(&handle_destroyer);
+    target.AddPreTargetHandler(h3);
+
+    h1.set_expect_pre_target(true);
+    handle_destroyer.set_expect_pre_target(true);
+    // |h3| should not receive events since |handle_destroyer| will have
+    // destroyed it.
+    h3->set_expect_pre_target(false);
+
+    MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0,
+                     0);
+    EventDispatchDetails details = dispatcher.ProcessEvent(&target, &mouse);
+    EXPECT_FALSE(details.dispatcher_destroyed);
+    EXPECT_FALSE(details.target_destroyed);
+    EXPECT_FALSE(mouse.stopped_propagation());
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(2, target.handler_list()[1]);
+  }
+
+  // Test for non-cancelable events.
+  {
+    TestEventDispatcher dispatcher;
+    TestTarget target;
+    TestEventHandler h1(1);
+    TestEventHandler* h3 = new TestEventHandler(3);
+    EventHandlerDestroyer handle_destroyer(2, h3);
+
+    target.AddPreTargetHandler(&h1);
+    target.AddPreTargetHandler(&handle_destroyer);
+    target.AddPreTargetHandler(h3);
+
+    h1.set_expect_pre_target(true);
+    handle_destroyer.set_expect_pre_target(true);
+    h3->set_expect_pre_target(false);
+
+    NonCancelableEvent event;
+    EventDispatchDetails details = dispatcher.ProcessEvent(&target, &event);
+    EXPECT_FALSE(details.dispatcher_destroyed);
+    EXPECT_FALSE(details.target_destroyed);
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(2, target.handler_list()[1]);
+  }
+}
+
+// Tests that things work correctly if an event-handler destroys both the
+// dispatcher and a handler.
+TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) {
+  {
+    TestEventDispatcher* dispatcher = new TestEventDispatcher();
+    TestTarget target;
+    TestEventHandler h1(1);
+    TestEventHandler* h3 = new TestEventHandler(3);
+    EventHandlerDestroyer destroyer(2, h3);
+
+    target.AddPreTargetHandler(&h1);
+    target.AddPreTargetHandler(&destroyer);
+    target.AddPreTargetHandler(h3);
+
+    h1.set_expect_pre_target(true);
+    destroyer.set_expect_pre_target(true);
+    destroyer.set_dispatcher_delegate(dispatcher);
+    // |h3| should not receive events since |destroyer| will have destroyed
+    // it.
+    h3->set_expect_pre_target(false);
+
+    MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0,
+                     0);
+    EventDispatchDetails details = dispatcher->ProcessEvent(&target, &mouse);
+    EXPECT_TRUE(details.dispatcher_destroyed);
+    EXPECT_TRUE(mouse.stopped_propagation());
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(2, target.handler_list()[1]);
+  }
+
+  // Test for non-cancelable events.
+  {
+    TestEventDispatcher* dispatcher = new TestEventDispatcher();
+    TestTarget target;
+    TestEventHandler h1(1);
+    TestEventHandler* h3 = new TestEventHandler(3);
+    EventHandlerDestroyer destroyer(2, h3);
+
+    target.AddPreTargetHandler(&h1);
+    target.AddPreTargetHandler(&destroyer);
+    target.AddPreTargetHandler(h3);
+
+    h1.set_expect_pre_target(true);
+    destroyer.set_expect_pre_target(true);
+    destroyer.set_dispatcher_delegate(dispatcher);
+    // |h3| should not receive events since |destroyer| will have destroyed
+    // it.
+    h3->set_expect_pre_target(false);
+
+    NonCancelableEvent event;
+    EventDispatchDetails details = dispatcher->ProcessEvent(&target, &event);
+    EXPECT_TRUE(details.dispatcher_destroyed);
+    EXPECT_EQ(2U, target.handler_list().size());
+    EXPECT_EQ(1, target.handler_list()[0]);
+    EXPECT_EQ(2, target.handler_list()[1]);
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/event_handler.cc b/ui/events/event_handler.cc
new file mode 100644
index 0000000..0f79c35
--- /dev/null
+++ b/ui/events/event_handler.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 "ui/events/event_handler.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_dispatcher.h"
+
+namespace ui {
+
+EventHandler::EventHandler() {
+}
+
+EventHandler::~EventHandler() {
+  while (!dispatchers_.empty()) {
+    EventDispatcher* dispatcher = dispatchers_.top();
+    dispatchers_.pop();
+    dispatcher->OnHandlerDestroyed(this);
+  }
+}
+
+void EventHandler::OnEvent(Event* event) {
+  // TODO(tdanderson): Encapsulate static_casts in ui::Event for all
+  //                   event types.
+  if (event->IsKeyEvent())
+    OnKeyEvent(static_cast<KeyEvent*>(event));
+  else if (event->IsMouseEvent())
+    OnMouseEvent(static_cast<MouseEvent*>(event));
+  else if (event->IsScrollEvent())
+    OnScrollEvent(static_cast<ScrollEvent*>(event));
+  else if (event->IsTouchEvent())
+    OnTouchEvent(static_cast<TouchEvent*>(event));
+  else if (event->IsGestureEvent())
+    OnGestureEvent(event->AsGestureEvent());
+  else if (event->type() == ET_CANCEL_MODE)
+    OnCancelMode(static_cast<CancelModeEvent*>(event));
+}
+
+void EventHandler::OnKeyEvent(KeyEvent* event) {
+}
+
+void EventHandler::OnMouseEvent(MouseEvent* event) {
+}
+
+void EventHandler::OnScrollEvent(ScrollEvent* event) {
+}
+
+void EventHandler::OnTouchEvent(TouchEvent* event) {
+}
+
+void EventHandler::OnGestureEvent(GestureEvent* event) {
+}
+
+void EventHandler::OnCancelMode(CancelModeEvent* event) {
+}
+
+}  // namespace ui
diff --git a/ui/events/event_handler.h b/ui/events/event_handler.h
new file mode 100644
index 0000000..088c3f8
--- /dev/null
+++ b/ui/events/event_handler.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 UI_EVENTS_EVENT_HANDLER_H_
+#define UI_EVENTS_EVENT_HANDLER_H_
+
+#include <stack>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class CancelModeEvent;
+class Event;
+class EventDispatcher;
+class EventTarget;
+class GestureEvent;
+class KeyEvent;
+class MouseEvent;
+class ScrollEvent;
+class TouchEvent;
+
+// Dispatches events to appropriate targets.  The default implementations of
+// all of the specific handlers (e.g. OnKeyEvent, OnMouseEvent) do nothing.
+class EVENTS_EXPORT EventHandler {
+ public:
+  EventHandler();
+  virtual ~EventHandler();
+
+  // This is called for all events. The default implementation routes the event
+  // to one of the event-specific callbacks (OnKeyEvent, OnMouseEvent etc.). If
+  // this is overridden, then normally, the override should chain into the
+  // default implementation for un-handled events.
+  virtual void OnEvent(Event* event);
+
+  virtual void OnKeyEvent(KeyEvent* event);
+
+  virtual void OnMouseEvent(MouseEvent* event);
+
+  virtual void OnScrollEvent(ScrollEvent* event);
+
+  virtual void OnTouchEvent(TouchEvent* event);
+
+  virtual void OnGestureEvent(GestureEvent* event);
+
+  virtual void OnCancelMode(CancelModeEvent* event);
+
+ private:
+  friend class EventDispatcher;
+
+  // EventDispatcher pushes itself on the top of this stack while dispatching
+  // events to this then pops itself off when done.
+  std::stack<EventDispatcher*> dispatchers_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventHandler);
+};
+
+typedef std::vector<EventHandler*> EventHandlerList;
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_HANDLER_H_
diff --git a/ui/events/event_processor.cc b/ui/events/event_processor.cc
new file mode 100644
index 0000000..b508508
--- /dev/null
+++ b/ui/events/event_processor.cc
@@ -0,0 +1,63 @@
+// Copyright 2013 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 "ui/events/event_processor.h"
+
+#include "ui/events/event_target.h"
+#include "ui/events/event_targeter.h"
+
+namespace ui {
+
+EventDispatchDetails EventProcessor::OnEventFromSource(Event* event) {
+  EventTarget* root = GetRootTarget();
+  CHECK(root);
+  EventTargeter* targeter = root->GetEventTargeter();
+  CHECK(targeter);
+
+  // If |event| is in the process of being dispatched or has already been
+  // dispatched, then dispatch a copy of the event instead.
+  bool dispatch_original_event = event->phase() == EP_PREDISPATCH;
+  Event* event_to_dispatch = event;
+  scoped_ptr<Event> event_copy;
+  if (!dispatch_original_event) {
+    event_copy = Event::Clone(*event);
+    event_to_dispatch = event_copy.get();
+  }
+
+  OnEventProcessingStarted(event_to_dispatch);
+  EventTarget* target = NULL;
+  if (!event_to_dispatch->handled())
+    target = targeter->FindTargetForEvent(root, event_to_dispatch);
+
+  EventDispatchDetails details;
+  while (target) {
+    details = DispatchEvent(target, event_to_dispatch);
+
+    if (!dispatch_original_event) {
+      if (event_to_dispatch->stopped_propagation())
+        event->StopPropagation();
+      else if (event_to_dispatch->handled())
+        event->SetHandled();
+    }
+
+    if (details.dispatcher_destroyed)
+      return details;
+
+    if (details.target_destroyed || event->handled())
+      break;
+
+    target = targeter->FindNextBestTarget(target, event_to_dispatch);
+  }
+
+  OnEventProcessingFinished(event);
+  return details;
+}
+
+void EventProcessor::OnEventProcessingStarted(Event* event) {
+}
+
+void EventProcessor::OnEventProcessingFinished(Event* event) {
+}
+
+}  // namespace ui
diff --git a/ui/events/event_processor.h b/ui/events/event_processor.h
new file mode 100644
index 0000000..676d920
--- /dev/null
+++ b/ui/events/event_processor.h
@@ -0,0 +1,49 @@
+// Copyright 2013 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 UI_EVENTS_EVENT_PROCESSOR_H_
+#define UI_EVENTS_EVENT_PROCESSOR_H_
+
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/event_source.h"
+
+namespace ui {
+
+// EventProcessor receives an event from an EventSource and dispatches it to a
+// tree of EventTargets.
+class EVENTS_EXPORT EventProcessor : public EventDispatcherDelegate {
+ public:
+  virtual ~EventProcessor() {}
+
+  // Returns the root of the tree this event processor owns.
+  virtual EventTarget* GetRootTarget() = 0;
+
+  // Dispatches an event received from the EventSource to the tree of
+  // EventTargets (whose root is returned by GetRootTarget()).  The co-ordinate
+  // space of the source must be the same as the root target, except that the
+  // target may have a high-dpi scale applied.
+  // TODO(tdanderson|sadrul): This is only virtual for testing purposes. It
+  //                          should not be virtual at all.
+  virtual EventDispatchDetails OnEventFromSource(Event* event)
+      WARN_UNUSED_RESULT;
+
+ protected:
+  // Invoked at the start of processing, before an EventTargeter is used to
+  // find the target of the event. If processing should not take place, marks
+  // |event| as handled. Otherwise updates |event| so that the targeter can
+  // operate correctly (e.g., it can be used to update the location of the
+  // event when dispatching from an EventSource in high-DPI) and updates any
+  // members in the event processor as necessary.
+  virtual void OnEventProcessingStarted(Event* event);
+
+  // Invoked when the processing of |event| has finished (i.e., when no further
+  // dispatching of |event| will be performed by this EventProcessor). Note
+  // that the last target to which |event| was dispatched may have been
+  // destroyed.
+  virtual void OnEventProcessingFinished(Event* event);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_PROCESSOR_H_
diff --git a/ui/events/event_processor_unittest.cc b/ui/events/event_processor_unittest.cc
new file mode 100644
index 0000000..d6720a8
--- /dev/null
+++ b/ui/events/event_processor_unittest.cc
@@ -0,0 +1,525 @@
+// Copyright (c) 2013 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 <vector>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_targeter.h"
+#include "ui/events/test/events_test_utils.h"
+#include "ui/events/test/test_event_handler.h"
+#include "ui/events/test/test_event_processor.h"
+#include "ui/events/test/test_event_target.h"
+
+typedef std::vector<std::string> HandlerSequenceRecorder;
+
+namespace ui {
+namespace test {
+
+class EventProcessorTest : public testing::Test {
+ public:
+  EventProcessorTest() {}
+  virtual ~EventProcessorTest() {}
+
+  // testing::Test:
+  virtual void SetUp() OVERRIDE {
+    processor_.SetRoot(scoped_ptr<EventTarget>(new TestEventTarget()));
+    processor_.Reset();
+    root()->SetEventTargeter(make_scoped_ptr(new EventTargeter()));
+  }
+
+  TestEventTarget* root() {
+    return static_cast<TestEventTarget*>(processor_.GetRootTarget());
+  }
+
+  TestEventProcessor* processor() {
+    return &processor_;
+  }
+
+  void DispatchEvent(Event* event) {
+    processor_.OnEventFromSource(event);
+  }
+
+ protected:
+  TestEventProcessor processor_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventProcessorTest);
+};
+
+TEST_F(EventProcessorTest, Basic) {
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  root()->AddChild(child.Pass());
+
+  MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10),
+                   EF_NONE, EF_NONE);
+  DispatchEvent(&mouse);
+  EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+
+  root()->RemoveChild(root()->child_at(0));
+  DispatchEvent(&mouse);
+  EXPECT_TRUE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+}
+
+template<typename T>
+class BoundsEventTargeter : public EventTargeter {
+ public:
+  virtual ~BoundsEventTargeter() {}
+
+ protected:
+  virtual bool SubtreeShouldBeExploredForEvent(
+      EventTarget* target, const LocatedEvent& event) OVERRIDE {
+    T* t = static_cast<T*>(target);
+    return (t->bounds().Contains(event.location()));
+  }
+};
+
+class BoundsTestTarget : public TestEventTarget {
+ public:
+  BoundsTestTarget() {}
+  virtual ~BoundsTestTarget() {}
+
+  void set_bounds(gfx::Rect rect) { bounds_ = rect; }
+  gfx::Rect bounds() const { return bounds_; }
+
+  static void ConvertPointToTarget(BoundsTestTarget* source,
+                                   BoundsTestTarget* target,
+                                   gfx::Point* location) {
+    gfx::Vector2d vector;
+    if (source->Contains(target)) {
+      for (; target && target != source;
+           target = static_cast<BoundsTestTarget*>(target->parent())) {
+        vector += target->bounds().OffsetFromOrigin();
+      }
+      *location -= vector;
+    } else if (target->Contains(source)) {
+      for (; source && source != target;
+           source = static_cast<BoundsTestTarget*>(source->parent())) {
+        vector += source->bounds().OffsetFromOrigin();
+      }
+      *location += vector;
+    } else {
+      NOTREACHED();
+    }
+  }
+
+ private:
+  // EventTarget:
+  virtual void ConvertEventToTarget(EventTarget* target,
+                                    LocatedEvent* event) OVERRIDE {
+    event->ConvertLocationToTarget(this,
+                                   static_cast<BoundsTestTarget*>(target));
+  }
+
+  gfx::Rect bounds_;
+
+  DISALLOW_COPY_AND_ASSIGN(BoundsTestTarget);
+};
+
+TEST_F(EventProcessorTest, Bounds) {
+  scoped_ptr<BoundsTestTarget> parent(new BoundsTestTarget());
+  scoped_ptr<BoundsTestTarget> child(new BoundsTestTarget());
+  scoped_ptr<BoundsTestTarget> grandchild(new BoundsTestTarget());
+
+  parent->set_bounds(gfx::Rect(0, 0, 30, 30));
+  child->set_bounds(gfx::Rect(5, 5, 20, 20));
+  grandchild->set_bounds(gfx::Rect(5, 5, 5, 5));
+
+  child->AddChild(scoped_ptr<TestEventTarget>(grandchild.Pass()));
+  parent->AddChild(scoped_ptr<TestEventTarget>(child.Pass()));
+  root()->AddChild(scoped_ptr<TestEventTarget>(parent.Pass()));
+
+  ASSERT_EQ(1u, root()->child_count());
+  ASSERT_EQ(1u, root()->child_at(0)->child_count());
+  ASSERT_EQ(1u, root()->child_at(0)->child_at(0)->child_count());
+
+  TestEventTarget* parent_r = root()->child_at(0);
+  TestEventTarget* child_r = parent_r->child_at(0);
+  TestEventTarget* grandchild_r = child_r->child_at(0);
+
+  // Dispatch a mouse event that falls on the parent, but not on the child. When
+  // the default event-targeter used, the event will still reach |grandchild|,
+  // because the default targeter does not look at the bounds.
+  MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(1, 1), EF_NONE,
+                   EF_NONE);
+  DispatchEvent(&mouse);
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  grandchild_r->ResetReceivedEvents();
+
+  // Now install a targeter on the parent that looks at the bounds and makes
+  // sure the event reaches the target only if the location of the event within
+  // the bounds of the target.
+  MouseEvent mouse2(ET_MOUSE_MOVED, gfx::Point(1, 1), gfx::Point(1, 1), EF_NONE,
+                    EF_NONE);
+  parent_r->SetEventTargeter(scoped_ptr<EventTargeter>(
+      new BoundsEventTargeter<BoundsTestTarget>()));
+  DispatchEvent(&mouse2);
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  parent_r->ResetReceivedEvents();
+
+  MouseEvent second(ET_MOUSE_MOVED, gfx::Point(12, 12), gfx::Point(12, 12),
+                    EF_NONE, EF_NONE);
+  DispatchEvent(&second);
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(child_r->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
+}
+
+// ReDispatchEventHandler is used to receive mouse events and forward them
+// to a specified EventProcessor. Verifies that the event has the correct
+// target and phase both before and after the nested event processing. Also
+// verifies that the location of the event remains the same after it has
+// been processed by the second EventProcessor.
+class ReDispatchEventHandler : public TestEventHandler {
+ public:
+  ReDispatchEventHandler(EventProcessor* processor, EventTarget* target)
+      : processor_(processor), expected_target_(target) {}
+  virtual ~ReDispatchEventHandler() {}
+
+  // TestEventHandler:
+  virtual void OnMouseEvent(MouseEvent* event) OVERRIDE {
+    TestEventHandler::OnMouseEvent(event);
+
+    EXPECT_EQ(expected_target_, event->target());
+    EXPECT_EQ(EP_TARGET, event->phase());
+
+    gfx::Point location(event->location());
+    EventDispatchDetails details = processor_->OnEventFromSource(event);
+    EXPECT_FALSE(details.dispatcher_destroyed);
+    EXPECT_FALSE(details.target_destroyed);
+
+    // The nested event-processing should not have mutated the target,
+    // phase, or location of |event|.
+    EXPECT_EQ(expected_target_, event->target());
+    EXPECT_EQ(EP_TARGET, event->phase());
+    EXPECT_EQ(location, event->location());
+  }
+
+ private:
+  EventProcessor* processor_;
+  EventTarget* expected_target_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReDispatchEventHandler);
+};
+
+// Verifies that the phase and target information of an event is not mutated
+// as a result of sending the event to an event processor while it is still
+// being processed by another event processor.
+TEST_F(EventProcessorTest, NestedEventProcessing) {
+  // Add one child to the default event processor used in this test suite.
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  root()->AddChild(child.Pass());
+
+  // Define a second root target and child.
+  scoped_ptr<EventTarget> second_root_scoped(new TestEventTarget());
+  TestEventTarget* second_root =
+      static_cast<TestEventTarget*>(second_root_scoped.get());
+  second_root->SetEventTargeter(make_scoped_ptr(new EventTargeter()));
+  scoped_ptr<TestEventTarget> second_child(new TestEventTarget());
+  second_root->AddChild(second_child.Pass());
+
+  // Define a second event processor which owns the second root.
+  scoped_ptr<TestEventProcessor> second_processor(new TestEventProcessor());
+  second_processor->SetRoot(second_root_scoped.Pass());
+
+  // Indicate that an event which is dispatched to the child target owned by the
+  // first event processor should be handled by |target_handler| instead.
+  scoped_ptr<TestEventHandler> target_handler(
+      new ReDispatchEventHandler(second_processor.get(), root()->child_at(0)));
+  root()->child_at(0)->set_target_handler(target_handler.get());
+
+  // Dispatch a mouse event to the tree of event targets owned by the first
+  // event processor, checking in ReDispatchEventHandler that the phase and
+  // target information of the event is correct.
+  MouseEvent mouse(
+      ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), EF_NONE, EF_NONE);
+  DispatchEvent(&mouse);
+
+  // Verify also that |mouse| was seen by the child nodes contained in both
+  // event processors and that the event was not handled.
+  EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(second_root->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(mouse.handled());
+  second_root->child_at(0)->ResetReceivedEvents();
+  root()->child_at(0)->ResetReceivedEvents();
+
+  // Indicate that the child of the second root should handle events, and
+  // dispatch another mouse event to verify that it is marked as handled.
+  second_root->child_at(0)->set_mark_events_as_handled(true);
+  MouseEvent mouse2(
+      ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), EF_NONE, EF_NONE);
+  DispatchEvent(&mouse2);
+  EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(second_root->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(mouse2.handled());
+}
+
+// Verifies that OnEventProcessingFinished() is called when an event
+// has been handled.
+TEST_F(EventProcessorTest, OnEventProcessingFinished) {
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  child->set_mark_events_as_handled(true);
+  root()->AddChild(child.Pass());
+
+  // Dispatch a mouse event. We expect the event to be seen by the target,
+  // handled, and we expect OnEventProcessingFinished() to be invoked once.
+  MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10),
+                   EF_NONE, EF_NONE);
+  DispatchEvent(&mouse);
+  EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(mouse.handled());
+  EXPECT_EQ(1, processor()->num_times_processing_finished());
+}
+
+// Verifies that OnEventProcessingStarted() has been called when starting to
+// process an event, and that processing does not take place if
+// OnEventProcessingStarted() marks the event as handled. Also verifies that
+// OnEventProcessingFinished() is also called in either case.
+TEST_F(EventProcessorTest, OnEventProcessingStarted) {
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  root()->AddChild(child.Pass());
+
+  // Dispatch a mouse event. We expect the event to be seen by the target,
+  // OnEventProcessingStarted() should be called once, and
+  // OnEventProcessingFinished() should be called once. The event should
+  // remain unhandled.
+  MouseEvent mouse(
+      ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), EF_NONE, EF_NONE);
+  DispatchEvent(&mouse);
+  EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(mouse.handled());
+  EXPECT_EQ(1, processor()->num_times_processing_started());
+  EXPECT_EQ(1, processor()->num_times_processing_finished());
+  processor()->Reset();
+  root()->ResetReceivedEvents();
+  root()->child_at(0)->ResetReceivedEvents();
+
+  // Dispatch another mouse event, but with OnEventProcessingStarted() marking
+  // the event as handled to prevent processing. We expect the event to not be
+  // seen by the target this time, but OnEventProcessingStarted() and
+  // OnEventProcessingFinished() should both still be called once.
+  processor()->set_should_processing_occur(false);
+  MouseEvent mouse2(
+      ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10), EF_NONE, EF_NONE);
+  DispatchEvent(&mouse2);
+  EXPECT_FALSE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(mouse2.handled());
+  EXPECT_EQ(1, processor()->num_times_processing_started());
+  EXPECT_EQ(1, processor()->num_times_processing_finished());
+}
+
+class IgnoreEventTargeter : public EventTargeter {
+ public:
+  IgnoreEventTargeter() {}
+  virtual ~IgnoreEventTargeter() {}
+
+ private:
+  // EventTargeter:
+  virtual bool SubtreeShouldBeExploredForEvent(
+      EventTarget* target, const LocatedEvent& event) OVERRIDE {
+    return false;
+  }
+};
+
+// Verifies that the EventTargeter installed on an EventTarget can dictate
+// whether the target itself can process an event.
+TEST_F(EventProcessorTest, TargeterChecksOwningEventTarget) {
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  root()->AddChild(child.Pass());
+
+  MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10),
+                   EF_NONE, EF_NONE);
+  DispatchEvent(&mouse);
+  EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+  root()->child_at(0)->ResetReceivedEvents();
+
+  // Install an event handler on |child| which always prevents the target from
+  // receiving event.
+  root()->child_at(0)->SetEventTargeter(
+      scoped_ptr<EventTargeter>(new IgnoreEventTargeter()));
+  MouseEvent mouse2(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10),
+                    EF_NONE, EF_NONE);
+  DispatchEvent(&mouse2);
+  EXPECT_FALSE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
+  EXPECT_TRUE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
+}
+
+// An EventTargeter which is used to allow a bubbling behaviour in event
+// dispatch: if an event is not handled after being dispatched to its
+// initial target, the event is dispatched to the next-best target as
+// specified by FindNextBestTarget().
+class BubblingEventTargeter : public EventTargeter {
+ public:
+  explicit BubblingEventTargeter(TestEventTarget* initial_target)
+    : initial_target_(initial_target) {}
+  virtual ~BubblingEventTargeter() {}
+
+ private:
+  // EventTargeter:
+  virtual EventTarget* FindTargetForEvent(EventTarget* root,
+                                          Event* event) OVERRIDE {
+    return initial_target_;
+  }
+
+  virtual EventTarget* FindNextBestTarget(EventTarget* previous_target,
+                                          Event* event) OVERRIDE {
+    return previous_target->GetParentTarget();
+  }
+
+  TestEventTarget* initial_target_;
+
+  DISALLOW_COPY_AND_ASSIGN(BubblingEventTargeter);
+};
+
+// Tests that unhandled events are correctly dispatched to the next-best
+// target as decided by the BubblingEventTargeter.
+TEST_F(EventProcessorTest, DispatchToNextBestTarget) {
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  scoped_ptr<TestEventTarget> grandchild(new TestEventTarget());
+
+  root()->SetEventTargeter(
+      scoped_ptr<EventTargeter>(new BubblingEventTargeter(grandchild.get())));
+  child->AddChild(grandchild.Pass());
+  root()->AddChild(child.Pass());
+
+  ASSERT_EQ(1u, root()->child_count());
+  ASSERT_EQ(1u, root()->child_at(0)->child_count());
+  ASSERT_EQ(0u, root()->child_at(0)->child_at(0)->child_count());
+
+  TestEventTarget* child_r = root()->child_at(0);
+  TestEventTarget* grandchild_r = child_r->child_at(0);
+
+  // When the root has a BubblingEventTargeter installed, events targeted
+  // at the grandchild target should be dispatched to all three targets.
+  KeyEvent key_event(ET_KEY_PRESSED, VKEY_ESCAPE, EF_NONE);
+  DispatchEvent(&key_event);
+  EXPECT_TRUE(root()->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_TRUE(child_r->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED));
+  root()->ResetReceivedEvents();
+  child_r->ResetReceivedEvents();
+  grandchild_r->ResetReceivedEvents();
+
+  // Add a pre-target handler on the child of the root that will mark the event
+  // as handled. No targets in the hierarchy should receive the event.
+  TestEventHandler handler;
+  child_r->AddPreTargetHandler(&handler);
+  key_event = KeyEvent(ET_KEY_PRESSED, VKEY_ESCAPE, EF_NONE);
+  DispatchEvent(&key_event);
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_FALSE(child_r->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_FALSE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_EQ(1, handler.num_key_events());
+  handler.Reset();
+
+  // Add a post-target handler on the child of the root that will mark the event
+  // as handled. Only the grandchild (the initial target) should receive the
+  // event.
+  child_r->RemovePreTargetHandler(&handler);
+  child_r->AddPostTargetHandler(&handler);
+  key_event = KeyEvent(ET_KEY_PRESSED, VKEY_ESCAPE, EF_NONE);
+  DispatchEvent(&key_event);
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_FALSE(child_r->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_EQ(1, handler.num_key_events());
+  handler.Reset();
+  grandchild_r->ResetReceivedEvents();
+  child_r->RemovePostTargetHandler(&handler);
+
+  // Mark the event as handled when it reaches the EP_TARGET phase of
+  // dispatch at the child of the root. The child and grandchild
+  // targets should both receive the event, but the root should not.
+  child_r->set_mark_events_as_handled(true);
+  key_event = KeyEvent(ET_KEY_PRESSED, VKEY_ESCAPE, EF_NONE);
+  DispatchEvent(&key_event);
+  EXPECT_FALSE(root()->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_TRUE(child_r->DidReceiveEvent(ET_KEY_PRESSED));
+  EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_KEY_PRESSED));
+  root()->ResetReceivedEvents();
+  child_r->ResetReceivedEvents();
+  grandchild_r->ResetReceivedEvents();
+  child_r->set_mark_events_as_handled(false);
+}
+
+// Tests that unhandled events are seen by the correct sequence of
+// targets, pre-target handlers, and post-target handlers when
+// a BubblingEventTargeter is installed on the root target.
+TEST_F(EventProcessorTest, HandlerSequence) {
+  scoped_ptr<TestEventTarget> child(new TestEventTarget());
+  scoped_ptr<TestEventTarget> grandchild(new TestEventTarget());
+
+  root()->SetEventTargeter(
+      scoped_ptr<EventTargeter>(new BubblingEventTargeter(grandchild.get())));
+  child->AddChild(grandchild.Pass());
+  root()->AddChild(child.Pass());
+
+  ASSERT_EQ(1u, root()->child_count());
+  ASSERT_EQ(1u, root()->child_at(0)->child_count());
+  ASSERT_EQ(0u, root()->child_at(0)->child_at(0)->child_count());
+
+  TestEventTarget* child_r = root()->child_at(0);
+  TestEventTarget* grandchild_r = child_r->child_at(0);
+
+  HandlerSequenceRecorder recorder;
+  root()->set_target_name("R");
+  root()->set_recorder(&recorder);
+  child_r->set_target_name("C");
+  child_r->set_recorder(&recorder);
+  grandchild_r->set_target_name("G");
+  grandchild_r->set_recorder(&recorder);
+
+  TestEventHandler pre_root;
+  pre_root.set_handler_name("PreR");
+  pre_root.set_recorder(&recorder);
+  root()->AddPreTargetHandler(&pre_root);
+
+  TestEventHandler pre_child;
+  pre_child.set_handler_name("PreC");
+  pre_child.set_recorder(&recorder);
+  child_r->AddPreTargetHandler(&pre_child);
+
+  TestEventHandler pre_grandchild;
+  pre_grandchild.set_handler_name("PreG");
+  pre_grandchild.set_recorder(&recorder);
+  grandchild_r->AddPreTargetHandler(&pre_grandchild);
+
+  TestEventHandler post_root;
+  post_root.set_handler_name("PostR");
+  post_root.set_recorder(&recorder);
+  root()->AddPostTargetHandler(&post_root);
+
+  TestEventHandler post_child;
+  post_child.set_handler_name("PostC");
+  post_child.set_recorder(&recorder);
+  child_r->AddPostTargetHandler(&post_child);
+
+  TestEventHandler post_grandchild;
+  post_grandchild.set_handler_name("PostG");
+  post_grandchild.set_recorder(&recorder);
+  grandchild_r->AddPostTargetHandler(&post_grandchild);
+
+  MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10),
+                   EF_NONE, EF_NONE);
+  DispatchEvent(&mouse);
+
+  std::string expected[] = { "PreR", "PreC", "PreG", "G", "PostG", "PostC",
+      "PostR", "PreR", "PreC", "C", "PostC", "PostR", "PreR", "R", "PostR" };
+  EXPECT_EQ(std::vector<std::string>(
+      expected, expected + arraysize(expected)), recorder);
+}
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/event_rewriter.h b/ui/events/event_rewriter.h
new file mode 100644
index 0000000..f948725
--- /dev/null
+++ b/ui/events/event_rewriter.h
@@ -0,0 +1,68 @@
+// Copyright 2014 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 UI_EVENTS_EVENT_REWRITER_H_
+#define UI_EVENTS_EVENT_REWRITER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class Event;
+
+// Return status of EventRewriter operations; see that class below.
+enum EventRewriteStatus {
+  // Nothing was done; no rewritten event returned. Pass the original
+  // event to later rewriters, or send it to the EventProcessor if this
+  // was the final rewriter.
+  EVENT_REWRITE_CONTINUE,
+
+  // The event has been rewritten. Send the rewritten event to the
+  // EventProcessor instead of the original event (without sending
+  // either to any later rewriters).
+  EVENT_REWRITE_REWRITTEN,
+
+  // The event should be discarded, neither passing it to any later
+  // rewriters nor sending it to the EventProcessor.
+  EVENT_REWRITE_DISCARD,
+
+  // The event has been rewritten. As for EVENT_REWRITE_REWRITTEN,
+  // send the rewritten event to the EventProcessor instead of the
+  // original event (without sending either to any later rewriters).
+  // In addition the rewriter has one or more additional new events
+  // to be retrieved using |NextDispatchEvent()| and sent to the
+  // EventProcessor.
+  EVENT_REWRITE_DISPATCH_ANOTHER,
+};
+
+// EventRewriter provides a mechanism for Events to be rewritten
+// before being dispatched from EventSource to EventProcessor.
+class EVENTS_EXPORT EventRewriter {
+ public:
+  virtual ~EventRewriter() {}
+
+  // Potentially rewrites (replaces) an event, or requests it be discarded.
+  // or discards an event. If the rewriter wants to rewrite an event, and
+  // dispatch another event once the rewritten event is dispatched, it should
+  // return EVENT_REWRITE_DISPATCH_ANOTHER, and return the next event to
+  // dispatch from |NextDispatchEvent()|.
+  virtual EventRewriteStatus RewriteEvent(
+      const Event& event,
+      scoped_ptr<Event>* rewritten_event) = 0;
+
+  // Supplies an additional event to be dispatched. It is only valid to
+  // call this after the immediately previous call to |RewriteEvent()|
+  // or |NextDispatchEvent()| has returned EVENT_REWRITE_DISPATCH_ANOTHER.
+  // Should only return either EVENT_REWRITE_REWRITTEN or
+  // EVENT_REWRITE_DISPATCH_ANOTHER; otherwise the previous call should not
+  // have returned EVENT_REWRITE_DISPATCH_ANOTHER.
+  virtual EventRewriteStatus NextDispatchEvent(
+      const Event& last_event,
+      scoped_ptr<Event>* new_event) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_REWRITER_H_
diff --git a/ui/events/event_rewriter_unittest.cc b/ui/events/event_rewriter_unittest.cc
new file mode 100644
index 0000000..11a05c9
--- /dev/null
+++ b/ui/events/event_rewriter_unittest.cc
@@ -0,0 +1,231 @@
+// Copyright 2014 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 "ui/events/event_rewriter.h"
+
+#include <list>
+#include <map>
+#include <set>
+#include <utility>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/test/test_event_processor.h"
+
+namespace ui {
+
+namespace {
+
+// To test the handling of |EventRewriter|s through |EventSource|,
+// we rewrite and test event types.
+class TestEvent : public Event {
+ public:
+  explicit TestEvent(EventType type)
+      : Event(type, base::TimeDelta(), 0), unique_id_(next_unique_id_++) {}
+  virtual ~TestEvent() {}
+  int unique_id() const { return unique_id_; }
+
+ private:
+  static int next_unique_id_;
+  int unique_id_;
+};
+
+int TestEvent::next_unique_id_ = 0;
+
+// TestEventRewriteProcessor is set up with a sequence of event types,
+// and fails if the events received via OnEventFromSource() do not match
+// this sequence. These expected event types are consumed on receipt.
+class TestEventRewriteProcessor : public test::TestEventProcessor {
+ public:
+  TestEventRewriteProcessor() {}
+  virtual ~TestEventRewriteProcessor() { CheckAllReceived(); }
+
+  void AddExpectedEvent(EventType type) { expected_events_.push_back(type); }
+  // Test that all expected events have been received.
+  void CheckAllReceived() { EXPECT_TRUE(expected_events_.empty()); }
+
+  // EventProcessor:
+  virtual EventDispatchDetails OnEventFromSource(Event* event) OVERRIDE {
+    EXPECT_FALSE(expected_events_.empty());
+    EXPECT_EQ(expected_events_.front(), event->type());
+    expected_events_.pop_front();
+    return EventDispatchDetails();
+  }
+
+ private:
+  std::list<EventType> expected_events_;
+  DISALLOW_COPY_AND_ASSIGN(TestEventRewriteProcessor);
+};
+
+// Trivial EventSource that does nothing but send events.
+class TestEventRewriteSource : public EventSource {
+ public:
+  explicit TestEventRewriteSource(EventProcessor* processor)
+      : processor_(processor) {}
+  virtual EventProcessor* GetEventProcessor() OVERRIDE { return processor_; }
+  void Send(EventType type) {
+    scoped_ptr<Event> event(new TestEvent(type));
+    (void)SendEventToProcessor(event.get());
+  }
+
+ private:
+  EventProcessor* processor_;
+};
+
+// This EventRewriter always returns the same status, and if rewriting, the
+// same event type; it is used to test simple rewriting, and rewriter addition,
+// removal, and sequencing. Consequently EVENT_REWRITE_DISPATCH_ANOTHER is not
+// supported here (calls to NextDispatchEvent() would continue indefinitely).
+class TestConstantEventRewriter : public EventRewriter {
+ public:
+  TestConstantEventRewriter(EventRewriteStatus status, EventType type)
+      : status_(status), type_(type) {
+    CHECK_NE(EVENT_REWRITE_DISPATCH_ANOTHER, status);
+  }
+
+  virtual EventRewriteStatus RewriteEvent(const Event& event,
+                                          scoped_ptr<Event>* rewritten_event)
+      OVERRIDE {
+    if (status_ == EVENT_REWRITE_REWRITTEN)
+      rewritten_event->reset(new TestEvent(type_));
+    return status_;
+  }
+  virtual EventRewriteStatus NextDispatchEvent(const Event& last_event,
+                                               scoped_ptr<Event>* new_event)
+      OVERRIDE {
+    NOTREACHED();
+    return status_;
+  }
+
+ private:
+  EventRewriteStatus status_;
+  EventType type_;
+};
+
+// This EventRewriter runs a simple state machine; it is used to test
+// EVENT_REWRITE_DISPATCH_ANOTHER.
+class TestStateMachineEventRewriter : public EventRewriter {
+ public:
+  TestStateMachineEventRewriter() : last_rewritten_event_(0), state_(0) {}
+  void AddRule(int from_state, EventType from_type,
+               int to_state, EventType to_type, EventRewriteStatus to_status) {
+    RewriteResult r = {to_state, to_type, to_status};
+    rules_.insert(std::pair<RewriteCase, RewriteResult>(
+        RewriteCase(from_state, from_type), r));
+  }
+  virtual EventRewriteStatus RewriteEvent(const Event& event,
+                                          scoped_ptr<Event>* rewritten_event)
+      OVERRIDE {
+    RewriteRules::iterator find =
+        rules_.find(RewriteCase(state_, event.type()));
+    if (find == rules_.end())
+      return EVENT_REWRITE_CONTINUE;
+    if ((find->second.status == EVENT_REWRITE_REWRITTEN) ||
+        (find->second.status == EVENT_REWRITE_DISPATCH_ANOTHER)) {
+      last_rewritten_event_ = new TestEvent(find->second.type);
+      rewritten_event->reset(last_rewritten_event_);
+    } else {
+      last_rewritten_event_ = 0;
+    }
+    state_ = find->second.state;
+    return find->second.status;
+  }
+  virtual EventRewriteStatus NextDispatchEvent(const Event& last_event,
+                                               scoped_ptr<Event>* new_event)
+      OVERRIDE {
+    EXPECT_TRUE(last_rewritten_event_);
+    const TestEvent* arg_last = static_cast<const TestEvent*>(&last_event);
+    EXPECT_EQ(last_rewritten_event_->unique_id(), arg_last->unique_id());
+    const TestEvent* arg_new = static_cast<const TestEvent*>(new_event->get());
+    EXPECT_FALSE(arg_new && arg_last->unique_id() == arg_new->unique_id());
+    return RewriteEvent(last_event, new_event);
+  }
+
+ private:
+  typedef std::pair<int, EventType> RewriteCase;
+  struct RewriteResult {
+    int state;
+    EventType type;
+    EventRewriteStatus status;
+  };
+  typedef std::map<RewriteCase, RewriteResult> RewriteRules;
+  RewriteRules rules_;
+  TestEvent* last_rewritten_event_;
+  int state_;
+};
+
+}  // namespace
+
+TEST(EventRewriterTest, EventRewriting) {
+  // TestEventRewriter r0 always rewrites events to ET_CANCEL_MODE;
+  // it is placed at the beginning of the chain and later removed,
+  // to verify that rewriter removal works.
+  TestConstantEventRewriter r0(EVENT_REWRITE_REWRITTEN, ET_CANCEL_MODE);
+
+  // TestEventRewriter r1 always returns EVENT_REWRITE_CONTINUE;
+  // it is placed at the beginning of the chain to verify that a
+  // later rewriter sees the events.
+  TestConstantEventRewriter r1(EVENT_REWRITE_CONTINUE, ET_UNKNOWN);
+
+  // TestEventRewriter r2 has a state machine, primarily to test
+  // |EVENT_REWRITE_DISPATCH_ANOTHER|.
+  TestStateMachineEventRewriter r2;
+
+  // TestEventRewriter r3 always rewrites events to ET_CANCEL_MODE;
+  // it is placed at the end of the chain to verify that previously
+  // rewritten events are not passed further down the chain.
+  TestConstantEventRewriter r3(EVENT_REWRITE_REWRITTEN, ET_CANCEL_MODE);
+
+  TestEventRewriteProcessor p;
+  TestEventRewriteSource s(&p);
+  s.AddEventRewriter(&r0);
+  s.AddEventRewriter(&r1);
+  s.AddEventRewriter(&r2);
+
+  // These events should be rewritten by r0 to ET_CANCEL_MODE.
+  p.AddExpectedEvent(ET_CANCEL_MODE);
+  s.Send(ET_MOUSE_DRAGGED);
+  p.AddExpectedEvent(ET_CANCEL_MODE);
+  s.Send(ET_MOUSE_PRESSED);
+  p.CheckAllReceived();
+
+  // Remove r0, and verify that it's gone and that events make it through.
+  s.AddEventRewriter(&r3);
+  s.RemoveEventRewriter(&r0);
+  r2.AddRule(0, ET_SCROLL_FLING_START,
+             0, ET_SCROLL_FLING_CANCEL, EVENT_REWRITE_REWRITTEN);
+  p.AddExpectedEvent(ET_SCROLL_FLING_CANCEL);
+  s.Send(ET_SCROLL_FLING_START);
+  p.CheckAllReceived();
+  s.RemoveEventRewriter(&r3);
+
+  // Verify EVENT_REWRITE_DISPATCH_ANOTHER using a state machine
+  // (that happens to be analogous to sticky keys).
+  r2.AddRule(0, ET_KEY_PRESSED,
+             1, ET_KEY_PRESSED, EVENT_REWRITE_CONTINUE);
+  r2.AddRule(1, ET_MOUSE_PRESSED,
+             0, ET_MOUSE_PRESSED, EVENT_REWRITE_CONTINUE);
+  r2.AddRule(1, ET_KEY_RELEASED,
+             2, ET_KEY_RELEASED, EVENT_REWRITE_DISCARD);
+  r2.AddRule(2, ET_MOUSE_RELEASED,
+             3, ET_MOUSE_RELEASED, EVENT_REWRITE_DISPATCH_ANOTHER);
+  r2.AddRule(3, ET_MOUSE_RELEASED,
+             0, ET_KEY_RELEASED, EVENT_REWRITE_REWRITTEN);
+  p.AddExpectedEvent(ET_KEY_PRESSED);
+  s.Send(ET_KEY_PRESSED);
+  s.Send(ET_KEY_RELEASED);
+  p.AddExpectedEvent(ET_MOUSE_PRESSED);
+  s.Send(ET_MOUSE_PRESSED);
+
+  // Removing rewriters r1 and r3 shouldn't affect r2.
+  s.RemoveEventRewriter(&r1);
+  s.RemoveEventRewriter(&r3);
+
+  // Continue with the state-based rewriting.
+  p.AddExpectedEvent(ET_MOUSE_RELEASED);
+  p.AddExpectedEvent(ET_KEY_RELEASED);
+  s.Send(ET_MOUSE_RELEASED);
+  p.CheckAllReceived();
+}
+
+}  // namespace ui
diff --git a/ui/events/event_source.cc b/ui/events/event_source.cc
new file mode 100644
index 0000000..0f3dfb8
--- /dev/null
+++ b/ui/events/event_source.cc
@@ -0,0 +1,76 @@
+// Copyright 2013 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 "ui/events/event_source.h"
+
+#include <algorithm>
+
+#include "ui/events/event_processor.h"
+#include "ui/events/event_rewriter.h"
+
+namespace ui {
+
+EventSource::EventSource() {}
+
+EventSource::~EventSource() {}
+
+void EventSource::AddEventRewriter(EventRewriter* rewriter) {
+  DCHECK(rewriter);
+  DCHECK(rewriter_list_.end() ==
+         std::find(rewriter_list_.begin(), rewriter_list_.end(), rewriter));
+  rewriter_list_.push_back(rewriter);
+}
+
+void EventSource::RemoveEventRewriter(EventRewriter* rewriter) {
+  EventRewriterList::iterator find =
+      std::find(rewriter_list_.begin(), rewriter_list_.end(), rewriter);
+  if (find != rewriter_list_.end())
+    rewriter_list_.erase(find);
+}
+
+EventDispatchDetails EventSource::SendEventToProcessor(Event* event) {
+  scoped_ptr<Event> rewritten_event;
+  EventRewriteStatus status = EVENT_REWRITE_CONTINUE;
+  EventRewriterList::const_iterator it = rewriter_list_.begin(),
+                                    end = rewriter_list_.end();
+  for (; it != end; ++it) {
+    status = (*it)->RewriteEvent(*event, &rewritten_event);
+    if (status == EVENT_REWRITE_DISCARD) {
+      CHECK(!rewritten_event);
+      return EventDispatchDetails();
+    }
+    if (status == EVENT_REWRITE_CONTINUE) {
+      CHECK(!rewritten_event);
+      continue;
+    }
+    break;
+  }
+  CHECK((it == end && !rewritten_event) || rewritten_event);
+  EventDispatchDetails details =
+      DeliverEventToProcessor(rewritten_event ? rewritten_event.get() : event);
+  if (details.dispatcher_destroyed)
+    return details;
+
+  while (status == EVENT_REWRITE_DISPATCH_ANOTHER) {
+    scoped_ptr<Event> new_event;
+    status = (*it)->NextDispatchEvent(*rewritten_event, &new_event);
+    if (status == EVENT_REWRITE_DISCARD)
+      return EventDispatchDetails();
+    CHECK_NE(EVENT_REWRITE_CONTINUE, status);
+    CHECK(new_event);
+    details = DeliverEventToProcessor(new_event.get());
+    if (details.dispatcher_destroyed)
+      return details;
+    rewritten_event.reset(new_event.release());
+  }
+  return EventDispatchDetails();
+}
+
+EventDispatchDetails EventSource::DeliverEventToProcessor(Event* event) {
+  EventProcessor* processor = GetEventProcessor();
+  CHECK(processor);
+  return processor->OnEventFromSource(event);
+}
+
+}  // namespace ui
diff --git a/ui/events/event_source.h b/ui/events/event_source.h
new file mode 100644
index 0000000..ca8b00f
--- /dev/null
+++ b/ui/events/event_source.h
@@ -0,0 +1,49 @@
+// Copyright 2013 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 UI_EVENTS_EVENT_SOURCE_H_
+#define UI_EVENTS_EVENT_SOURCE_H_
+
+#include <vector>
+
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class Event;
+class EventProcessor;
+class EventRewriter;
+
+// EventSource receives events from the native platform (e.g. X11, win32 etc.)
+// and sends the events to an EventProcessor.
+class EVENTS_EXPORT EventSource {
+ public:
+  EventSource();
+  virtual ~EventSource();
+
+  virtual EventProcessor* GetEventProcessor() = 0;
+
+  // Adds a rewriter to modify events before they are sent to the
+  // EventProcessor. The rewriter must be explicitly removed from the
+  // EventSource before the rewriter is destroyed. The EventSource
+  // does not take ownership of the rewriter.
+  void AddEventRewriter(EventRewriter* rewriter);
+  void RemoveEventRewriter(EventRewriter* rewriter);
+
+ protected:
+  EventDispatchDetails SendEventToProcessor(Event* event);
+
+ private:
+  friend class EventSourceTestApi;
+
+  typedef std::vector<EventRewriter*> EventRewriterList;
+  EventDispatchDetails DeliverEventToProcessor(Event* event);
+  EventRewriterList rewriter_list_;
+  DISALLOW_COPY_AND_ASSIGN(EventSource);
+};
+
+}  // namespace ui
+
+#endif // UI_EVENTS_EVENT_SOURCE_H_
diff --git a/ui/events/event_switches.cc b/ui/events/event_switches.cc
new file mode 100644
index 0000000..198113f
--- /dev/null
+++ b/ui/events/event_switches.cc
@@ -0,0 +1,42 @@
+// Copyright 2013 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 "ui/events/event_switches.h"
+
+namespace switches {
+
+// Enable scroll prediction for scroll update events.
+const char kEnableScrollPrediction[] = "enable-scroll-prediction";
+
+// Enable support for touch events.
+const char kTouchEvents[] = "touch-events";
+
+// The values the kTouchEvents switch may have, as in --touch-events=disabled.
+//   auto: enabled at startup when an attached touchscreen is present.
+const char kTouchEventsAuto[] = "auto";
+//   enabled: touch events always enabled.
+const char kTouchEventsEnabled[] = "enabled";
+//   disabled: touch events are disabled.
+const char kTouchEventsDisabled[] = "disabled";
+
+// Enable compensation for unstable pinch zoom. Some touch screens display
+// significant amount of wobble when moving a finger in a straight line. This
+// makes two finger scroll trigger an oscillating pinch zoom. See
+// crbug.com/394380 for details.
+const char kCompensateForUnstablePinchZoom[] =
+    "compensate-for-unstable-pinch-zoom";
+
+#if defined(OS_LINUX)
+// Tells chrome to interpret events from these devices as touch events. Only
+// available with XInput 2 (i.e. X server 1.8 or above). The id's of the
+// devices can be retrieved from 'xinput list'.
+const char kTouchDevices[] = "touch-devices";
+#endif
+
+#if defined(USE_XI2_MT) || defined(USE_OZONE)
+// The calibration factors given as "<left>,<right>,<top>,<bottom>".
+const char kTouchCalibration[] = "touch-calibration";
+#endif
+
+}  // namespace switches
diff --git a/ui/events/event_switches.h b/ui/events/event_switches.h
new file mode 100644
index 0000000..ff73d9c
--- /dev/null
+++ b/ui/events/event_switches.h
@@ -0,0 +1,30 @@
+// Copyright 2013 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 UI_EVENTS_EVENTS_SWITCHES_H_
+#define UI_EVENTS_EVENTS_SWITCHES_H_
+
+#include "base/compiler_specific.h"
+#include "ui/events/events_base_export.h"
+
+namespace switches {
+
+EVENTS_BASE_EXPORT extern const char kEnableScrollPrediction[];
+EVENTS_BASE_EXPORT extern const char kTouchEvents[];
+EVENTS_BASE_EXPORT extern const char kTouchEventsAuto[];
+EVENTS_BASE_EXPORT extern const char kTouchEventsEnabled[];
+EVENTS_BASE_EXPORT extern const char kTouchEventsDisabled[];
+EVENTS_BASE_EXPORT extern const char kCompensateForUnstablePinchZoom[];
+
+#if defined(OS_LINUX)
+EVENTS_BASE_EXPORT extern const char kTouchDevices[];
+#endif
+
+#if defined(USE_XI2_MT) || defined(USE_OZONE)
+EVENTS_BASE_EXPORT extern const char kTouchCalibration[];
+#endif
+
+}  // namespace switches
+
+#endif  // UI_EVENTS_EVENTS_SWITCHES_H_
diff --git a/ui/events/event_target.cc b/ui/events/event_target.cc
new file mode 100644
index 0000000..9b44f9a
--- /dev/null
+++ b/ui/events/event_target.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2012 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 "ui/events/event_target.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "ui/events/event.h"
+
+namespace ui {
+
+EventTarget::EventTarget()
+    : target_handler_(NULL) {
+}
+
+EventTarget::~EventTarget() {
+}
+
+void EventTarget::ConvertEventToTarget(EventTarget* target,
+                                       LocatedEvent* event) {
+}
+
+void EventTarget::AddPreTargetHandler(EventHandler* handler) {
+  pre_target_list_.push_back(handler);
+}
+
+void EventTarget::PrependPreTargetHandler(EventHandler* handler) {
+  pre_target_list_.insert(pre_target_list_.begin(), handler);
+}
+
+void EventTarget::RemovePreTargetHandler(EventHandler* handler) {
+  EventHandlerList::iterator find =
+      std::find(pre_target_list_.begin(),
+                pre_target_list_.end(),
+                handler);
+  if (find != pre_target_list_.end())
+    pre_target_list_.erase(find);
+}
+
+void EventTarget::AddPostTargetHandler(EventHandler* handler) {
+  post_target_list_.push_back(handler);
+}
+
+void EventTarget::RemovePostTargetHandler(EventHandler* handler) {
+  EventHandlerList::iterator find =
+      std::find(post_target_list_.begin(),
+                post_target_list_.end(),
+                handler);
+  if (find != post_target_list_.end())
+    post_target_list_.erase(find);
+}
+
+bool EventTarget::IsPreTargetListEmpty() const {
+  return pre_target_list_.empty();
+}
+
+void EventTarget::OnEvent(Event* event) {
+  CHECK_EQ(this, event->target());
+  if (target_handler_)
+    target_handler_->OnEvent(event);
+  else
+    EventHandler::OnEvent(event);
+}
+
+void EventTarget::OnKeyEvent(KeyEvent* event) {
+  CHECK_EQ(this, event->target());
+  if (target_handler_)
+    target_handler_->OnKeyEvent(event);
+}
+
+void EventTarget::OnMouseEvent(MouseEvent* event) {
+  CHECK_EQ(this, event->target());
+  if (target_handler_)
+    target_handler_->OnMouseEvent(event);
+}
+
+void EventTarget::OnScrollEvent(ScrollEvent* event) {
+  CHECK_EQ(this, event->target());
+  if (target_handler_)
+    target_handler_->OnScrollEvent(event);
+}
+
+void EventTarget::OnTouchEvent(TouchEvent* event) {
+  CHECK_EQ(this, event->target());
+  if (target_handler_)
+    target_handler_->OnTouchEvent(event);
+}
+
+void EventTarget::OnGestureEvent(GestureEvent* event) {
+  CHECK_EQ(this, event->target());
+  if (target_handler_)
+    target_handler_->OnGestureEvent(event);
+}
+
+void EventTarget::GetPreTargetHandlers(EventHandlerList* list) {
+  EventTarget* target = this;
+  while (target) {
+    EventHandlerList::reverse_iterator it, rend;
+    for (it = target->pre_target_list_.rbegin(),
+            rend = target->pre_target_list_.rend();
+        it != rend;
+        ++it) {
+      list->insert(list->begin(), *it);
+    }
+    target = target->GetParentTarget();
+  }
+}
+
+void EventTarget::GetPostTargetHandlers(EventHandlerList* list) {
+  EventTarget* target = this;
+  while (target) {
+    for (EventHandlerList::iterator it = target->post_target_list_.begin(),
+        end = target->post_target_list_.end(); it != end; ++it) {
+      list->push_back(*it);
+    }
+    target = target->GetParentTarget();
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/event_target.h b/ui/events/event_target.h
new file mode 100644
index 0000000..3ffec55
--- /dev/null
+++ b/ui/events/event_target.h
@@ -0,0 +1,118 @@
+// Copyright (c) 2012 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 UI_EVENTS_EVENT_TARGET_H_
+#define UI_EVENTS_EVENT_TARGET_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_handler.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class EventDispatcher;
+class EventTargeter;
+class EventTargetIterator;
+class LocatedEvent;
+
+class EVENTS_EXPORT EventTarget : public EventHandler {
+ public:
+  class DispatcherApi {
+   public:
+    explicit DispatcherApi(EventTarget* target) : target_(target) {}
+
+    const EventHandlerList& pre_target_list() const {
+      return target_->pre_target_list_;
+    }
+
+   private:
+    DispatcherApi();
+    EventTarget* target_;
+
+    DISALLOW_COPY_AND_ASSIGN(DispatcherApi);
+  };
+
+  EventTarget();
+  virtual ~EventTarget();
+
+  virtual bool CanAcceptEvent(const Event& event) = 0;
+
+  // Returns the parent EventTarget in the event-target tree.
+  virtual EventTarget* GetParentTarget() = 0;
+
+  // Returns an iterator an EventTargeter can use to iterate over the list of
+  // child EventTargets.
+  virtual scoped_ptr<EventTargetIterator> GetChildIterator() const = 0;
+
+  // Returns the EventTargeter that should be used to find the target for an
+  // event in the subtree rooted at this EventTarget.
+  virtual EventTargeter* GetEventTargeter() = 0;
+
+  // Updates the states in |event| (e.g. location) to be suitable for |target|,
+  // so that |event| can be dispatched to |target|.
+  virtual void ConvertEventToTarget(EventTarget* target,
+                                    LocatedEvent* event);
+
+  // Adds a handler to receive events before the target. The handler must be
+  // explicitly removed from the target before the handler is destroyed. The
+  // EventTarget does not take ownership of the handler.
+  void AddPreTargetHandler(EventHandler* handler);
+
+  // Same as AddPreTargetHandler except that the |handler| is added to the front
+  // of the list so it is the first one to receive events.
+  void PrependPreTargetHandler(EventHandler* handler);
+  void RemovePreTargetHandler(EventHandler* handler);
+
+  // Adds a handler to receive events after the target. The handler must be
+  // explicitly removed from the target before the handler is destroyed. The
+  // EventTarget does not take ownership of the handler.
+  void AddPostTargetHandler(EventHandler* handler);
+  void RemovePostTargetHandler(EventHandler* handler);
+
+  // Returns true if the event pre target list is empty.
+  bool IsPreTargetListEmpty() const;
+
+  void set_target_handler(EventHandler* handler) {
+    target_handler_ = handler;
+  }
+
+ protected:
+  EventHandler* target_handler() { return target_handler_; }
+
+  // Overridden from EventHandler:
+  virtual void OnEvent(Event* event) OVERRIDE;
+  virtual void OnKeyEvent(KeyEvent* event) OVERRIDE;
+  virtual void OnMouseEvent(MouseEvent* event) OVERRIDE;
+  virtual void OnScrollEvent(ScrollEvent* event) OVERRIDE;
+  virtual void OnTouchEvent(TouchEvent* event) OVERRIDE;
+  virtual void OnGestureEvent(GestureEvent* event) OVERRIDE;
+
+ private:
+  friend class EventDispatcher;
+  friend class EventTargetTestApi;
+
+  // Returns the list of handlers that should receive the event before the
+  // target. The handlers from the outermost target are first in the list, and
+  // the handlers on |this| are the last in the list.
+  void GetPreTargetHandlers(EventHandlerList* list);
+
+  // Returns the list of handlers that should receive the event after the
+  // target. The handlers from the outermost target are last in the list, and
+  // the handlers on |this| are the first in the list.
+  void GetPostTargetHandlers(EventHandlerList* list);
+
+  EventHandlerList pre_target_list_;
+  EventHandlerList post_target_list_;
+  EventHandler* target_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventTarget);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_TARGET_H_
diff --git a/ui/events/event_target_iterator.h b/ui/events/event_target_iterator.h
new file mode 100644
index 0000000..3083c83
--- /dev/null
+++ b/ui/events/event_target_iterator.h
@@ -0,0 +1,48 @@
+// Copyright 2013 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 UI_EVENTS_EVENT_TARGET_ITERATOR_H_
+#define UI_EVENTS_EVENT_TARGET_ITERATOR_H_
+
+#include <vector>
+
+namespace ui {
+
+class EventTarget;
+
+// An interface that allows iterating over a set of EventTargets.
+class EventTargetIterator {
+ public:
+  virtual ~EventTargetIterator() {}
+  virtual EventTarget* GetNextTarget() = 0;
+};
+
+// Provides an EventTargetIterator implementation for iterating over a list of
+// EventTargets. The list is iterated in the reverse order, since typically the
+// EventTargets are maintained in increasing z-order in the lists.
+template<typename T>
+class EventTargetIteratorImpl : public EventTargetIterator {
+ public:
+  explicit EventTargetIteratorImpl(const std::vector<T*>& children)
+      : begin_(children.rbegin()),
+        end_(children.rend()) {
+  }
+  virtual ~EventTargetIteratorImpl() {}
+
+  virtual EventTarget* GetNextTarget() OVERRIDE {
+    if (begin_ == end_)
+      return NULL;
+    EventTarget* target = *(begin_);
+    ++begin_;
+    return target;
+  }
+
+ private:
+  typename std::vector<T*>::const_reverse_iterator begin_;
+  typename std::vector<T*>::const_reverse_iterator end_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_TARGET_ITERATOR_H_
diff --git a/ui/events/event_targeter.cc b/ui/events/event_targeter.cc
new file mode 100644
index 0000000..3cdc83e
--- /dev/null
+++ b/ui/events/event_targeter.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2013 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 "ui/events/event_targeter.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_target.h"
+#include "ui/events/event_target_iterator.h"
+
+namespace ui {
+
+EventTargeter::~EventTargeter() {
+}
+
+EventTarget* EventTargeter::FindTargetForEvent(EventTarget* root,
+                                               Event* event) {
+  if (event->IsMouseEvent() ||
+      event->IsScrollEvent() ||
+      event->IsTouchEvent() ||
+      event->IsGestureEvent()) {
+    return FindTargetForLocatedEvent(root,
+                                     static_cast<LocatedEvent*>(event));
+  }
+  return root;
+}
+
+EventTarget* EventTargeter::FindTargetForLocatedEvent(EventTarget* root,
+                                                      LocatedEvent* event) {
+  scoped_ptr<EventTargetIterator> iter = root->GetChildIterator();
+  if (iter) {
+    EventTarget* target = root;
+    for (EventTarget* child = iter->GetNextTarget(); child;
+         child = iter->GetNextTarget()) {
+      EventTargeter* targeter = child->GetEventTargeter();
+      if (!targeter)
+        targeter = this;
+      if (!targeter->SubtreeShouldBeExploredForEvent(child, *event))
+        continue;
+      target->ConvertEventToTarget(child, event);
+      target = child;
+      EventTarget* child_target = targeter ?
+          targeter->FindTargetForLocatedEvent(child, event) :
+          FindTargetForLocatedEvent(child, event);
+      if (child_target)
+        return child_target;
+    }
+    target->ConvertEventToTarget(root, event);
+  }
+  return root->CanAcceptEvent(*event) ? root : NULL;
+}
+
+bool EventTargeter::SubtreeShouldBeExploredForEvent(EventTarget* target,
+                                                    const LocatedEvent& event) {
+  return SubtreeCanAcceptEvent(target, event) &&
+         EventLocationInsideBounds(target, event);
+}
+
+EventTarget* EventTargeter::FindNextBestTarget(EventTarget* previous_target,
+                                               Event* event) {
+  return NULL;
+}
+
+bool EventTargeter::SubtreeCanAcceptEvent(EventTarget* target,
+                                          const LocatedEvent& event) const {
+  return true;
+}
+
+bool EventTargeter::EventLocationInsideBounds(EventTarget* target,
+                                              const LocatedEvent& event) const {
+  return true;
+}
+
+}  // namespace ui
diff --git a/ui/events/event_targeter.h b/ui/events/event_targeter.h
new file mode 100644
index 0000000..23b1261
--- /dev/null
+++ b/ui/events/event_targeter.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2013 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 UI_EVENTS_EVENT_TARGETER_H_
+#define UI_EVENTS_EVENT_TARGETER_H_
+
+#include "base/compiler_specific.h"
+#include "ui/events/event.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class Event;
+class EventTarget;
+class LocatedEvent;
+
+class EVENTS_EXPORT EventTargeter {
+ public:
+  virtual ~EventTargeter();
+
+  // Returns the target |event| should be dispatched to. If there is no such
+  // target, return NULL. If |event| is a located event, the location of |event|
+  // is in the coordinate space of |root|. Furthermore, the targeter can mutate
+  // the event (e.g., by changing the location of the event to be in the
+  // returned target's coordinate space) so that it can be dispatched to the
+  // target without any further modification.
+  virtual EventTarget* FindTargetForEvent(EventTarget* root,
+                                          Event* event);
+
+  // Same as FindTargetForEvent(), but used for positional events. The location
+  // etc. of |event| are in |root|'s coordinate system. When finding the target
+  // for the event, the targeter can mutate the |event| (e.g. change the
+  // coordinate to be in the returned target's coordinate system) so that it can
+  // be dispatched to the target without any further modification.
+  // TODO(tdanderson|sadrul): This should not be in the public API of
+  //                          EventTargeter.
+  virtual EventTarget* FindTargetForLocatedEvent(EventTarget* root,
+                                                 LocatedEvent* event);
+
+  // Returns true if |target| or one of its descendants can be a target of
+  // |event|. This requires that |target| and its descendants are not
+  // prohibited from accepting the event, and that the event is within an
+  // actionable region of the target's bounds. Note that the location etc. of
+  // |event| is in |target|'s parent's coordinate system.
+  // TODO(tdanderson|sadrul): This function should be made non-virtual and
+  //                          non-public.
+  virtual bool SubtreeShouldBeExploredForEvent(EventTarget* target,
+                                               const LocatedEvent& event);
+
+  // Returns the next best target for |event| as compared to |previous_target|.
+  // |event| is in the local coordinate space of |previous_target|.
+  // Also mutates |event| so that it can be dispatched to the returned target
+  // (e.g., by changing |event|'s location to be in the returned target's
+  // coordinate space).
+  virtual EventTarget* FindNextBestTarget(EventTarget* previous_target,
+                                          Event* event);
+
+ protected:
+  // Returns false if neither |target| nor any of its descendants are allowed
+  // to accept |event| for reasons unrelated to the event's location or the
+  // target's bounds. For example, overrides of this function may consider
+  // attributes such as the visibility or enabledness of |target|. Note that
+  // the location etc. of |event| is in |target|'s parent's coordinate system.
+  virtual bool SubtreeCanAcceptEvent(EventTarget* target,
+                                     const LocatedEvent& event) const;
+
+  // Returns whether the location of the event is in an actionable region of the
+  // target. Note that the location etc. of |event| is in the |target|'s
+  // parent's coordinate system.
+  virtual bool EventLocationInsideBounds(EventTarget* target,
+                                         const LocatedEvent& event) const;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_TARGETER_H_
diff --git a/ui/events/event_unittest.cc b/ui/events/event_unittest.cc
new file mode 100644
index 0000000..46c2f63
--- /dev/null
+++ b/ui/events/event_unittest.cc
@@ -0,0 +1,452 @@
+// Copyright (c) 2012 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 "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+#include "ui/events/test/events_test_utils.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#include "ui/events/test/events_test_utils_x11.h"
+#include "ui/gfx/x/x11_types.h"
+#endif
+
+namespace ui {
+
+TEST(EventTest, NoNativeEvent) {
+  KeyEvent keyev(ET_KEY_PRESSED, VKEY_SPACE, EF_NONE);
+  EXPECT_FALSE(keyev.HasNativeEvent());
+}
+
+TEST(EventTest, NativeEvent) {
+#if defined(OS_WIN)
+  MSG native_event = { NULL, WM_KEYUP, VKEY_A, 0 };
+  KeyEvent keyev(native_event);
+  EXPECT_TRUE(keyev.HasNativeEvent());
+#elif defined(USE_X11)
+  ScopedXI2Event event;
+  event.InitKeyEvent(ET_KEY_RELEASED, VKEY_A, EF_NONE);
+  KeyEvent keyev(event);
+  EXPECT_TRUE(keyev.HasNativeEvent());
+#endif
+}
+
+TEST(EventTest, GetCharacter) {
+  // Check if Control+Enter returns 10.
+  KeyEvent keyev1(ET_KEY_PRESSED, VKEY_RETURN, EF_CONTROL_DOWN);
+  EXPECT_EQ(10, keyev1.GetCharacter());
+  // Check if Enter returns 13.
+  KeyEvent keyev2(ET_KEY_PRESSED, VKEY_RETURN, EF_NONE);
+  EXPECT_EQ(13, keyev2.GetCharacter());
+
+#if defined(USE_X11)
+  // For X11, test the functions with native_event() as well. crbug.com/107837
+  ScopedXI2Event event;
+  event.InitKeyEvent(ET_KEY_PRESSED, VKEY_RETURN, EF_CONTROL_DOWN);
+  KeyEvent keyev3(event);
+  EXPECT_EQ(10, keyev3.GetCharacter());
+
+  event.InitKeyEvent(ET_KEY_PRESSED, VKEY_RETURN, EF_NONE);
+  KeyEvent keyev4(event);
+  EXPECT_EQ(13, keyev4.GetCharacter());
+#endif
+}
+
+TEST(EventTest, ClickCount) {
+  const gfx::Point origin(0, 0);
+  MouseEvent mouseev(ET_MOUSE_PRESSED, origin, origin, 0, 0);
+  for (int i = 1; i <=3 ; ++i) {
+    mouseev.SetClickCount(i);
+    EXPECT_EQ(i, mouseev.GetClickCount());
+  }
+}
+
+TEST(EventTest, RepeatedClick) {
+  const gfx::Point origin(0, 0);
+  MouseEvent mouse_ev1(ET_MOUSE_PRESSED, origin, origin, 0, 0);
+  MouseEvent mouse_ev2(ET_MOUSE_PRESSED, origin, origin, 0, 0);
+  LocatedEventTestApi test_ev1(&mouse_ev1);
+  LocatedEventTestApi test_ev2(&mouse_ev2);
+
+  base::TimeDelta start = base::TimeDelta::FromMilliseconds(0);
+  base::TimeDelta soon = start + base::TimeDelta::FromMilliseconds(1);
+  base::TimeDelta later = start + base::TimeDelta::FromMilliseconds(1000);
+
+  // Close point.
+  test_ev1.set_location(gfx::Point(0, 0));
+  test_ev2.set_location(gfx::Point(1, 0));
+  test_ev1.set_time_stamp(start);
+  test_ev2.set_time_stamp(soon);
+  EXPECT_TRUE(MouseEvent::IsRepeatedClickEvent(mouse_ev1, mouse_ev2));
+
+  // Too far.
+  test_ev1.set_location(gfx::Point(0, 0));
+  test_ev2.set_location(gfx::Point(10, 0));
+  test_ev1.set_time_stamp(start);
+  test_ev2.set_time_stamp(soon);
+  EXPECT_FALSE(MouseEvent::IsRepeatedClickEvent(mouse_ev1, mouse_ev2));
+
+  // Too long a time between clicks.
+  test_ev1.set_location(gfx::Point(0, 0));
+  test_ev2.set_location(gfx::Point(0, 0));
+  test_ev1.set_time_stamp(start);
+  test_ev2.set_time_stamp(later);
+  EXPECT_FALSE(MouseEvent::IsRepeatedClickEvent(mouse_ev1, mouse_ev2));
+}
+
+// Tests that an event only increases the click count and gets marked as a
+// double click if a release event was seen for the previous click. This
+// prevents the same PRESSED event from being processed twice:
+// http://crbug.com/389162
+TEST(EventTest, DoubleClickRequiresRelease) {
+  const gfx::Point origin1(0, 0);
+  const gfx::Point origin2(100, 0);
+  scoped_ptr<MouseEvent> ev;
+  base::TimeDelta start = base::TimeDelta::FromMilliseconds(0);
+
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin1, origin1, 0, 0));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin1, origin1, 0, 0));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin2, origin2, 0, 0));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_RELEASED, origin2, origin2, 0, 0));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin2, origin2, 0, 0));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(2, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_RELEASED, origin2, origin2, 0, 0));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(2, MouseEvent::GetRepeatCount(*ev));
+  MouseEvent::ResetLastClickForTest();
+}
+
+// Tests that clicking right and then left clicking does not generate a double
+// click.
+TEST(EventTest, SingleClickRightLeft) {
+  const gfx::Point origin(0, 0);
+  scoped_ptr<MouseEvent> ev;
+  base::TimeDelta start = base::TimeDelta::FromMilliseconds(0);
+
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin, origin,
+                          ui::EF_RIGHT_MOUSE_BUTTON,
+                          ui::EF_RIGHT_MOUSE_BUTTON));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin, origin,
+                          ui::EF_LEFT_MOUSE_BUTTON,
+                          ui::EF_LEFT_MOUSE_BUTTON));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_RELEASED, origin, origin,
+                          ui::EF_LEFT_MOUSE_BUTTON,
+                          ui::EF_LEFT_MOUSE_BUTTON));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(1, MouseEvent::GetRepeatCount(*ev));
+  ev.reset(new MouseEvent(ET_MOUSE_PRESSED, origin, origin,
+                          ui::EF_LEFT_MOUSE_BUTTON,
+                          ui::EF_LEFT_MOUSE_BUTTON));
+  ev->set_time_stamp(start);
+  EXPECT_EQ(2, MouseEvent::GetRepeatCount(*ev));
+  MouseEvent::ResetLastClickForTest();
+}
+
+TEST(EventTest, KeyEvent) {
+  static const struct {
+    KeyboardCode key_code;
+    int flags;
+    uint16 character;
+  } kTestData[] = {
+    { VKEY_A, 0, 'a' },
+    { VKEY_A, EF_SHIFT_DOWN, 'A' },
+    { VKEY_A, EF_CAPS_LOCK_DOWN, 'A' },
+    { VKEY_A, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, 'a' },
+    { VKEY_A, EF_CONTROL_DOWN, 0x01 },
+    { VKEY_A, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x01' },
+    { VKEY_Z, 0, 'z' },
+    { VKEY_Z, EF_SHIFT_DOWN, 'Z' },
+    { VKEY_Z, EF_CAPS_LOCK_DOWN, 'Z' },
+    { VKEY_Z, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, 'z' },
+    { VKEY_Z, EF_CONTROL_DOWN, '\x1A' },
+    { VKEY_Z, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x1A' },
+
+    { VKEY_2, EF_CONTROL_DOWN, '\0' },
+    { VKEY_2, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+    { VKEY_6, EF_CONTROL_DOWN, '\0' },
+    { VKEY_6, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x1E' },
+    { VKEY_OEM_MINUS, EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_MINUS, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\x1F' },
+    { VKEY_OEM_4, EF_CONTROL_DOWN, '\x1B' },
+    { VKEY_OEM_4, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_5, EF_CONTROL_DOWN, '\x1C' },
+    { VKEY_OEM_5, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_6, EF_CONTROL_DOWN, '\x1D' },
+    { VKEY_OEM_6, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+    { VKEY_RETURN, EF_CONTROL_DOWN, '\x0A' },
+
+    { VKEY_0, 0, '0' },
+    { VKEY_0, EF_SHIFT_DOWN, ')' },
+    { VKEY_0, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, ')' },
+    { VKEY_0, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+
+    { VKEY_9, 0, '9' },
+    { VKEY_9, EF_SHIFT_DOWN, '(' },
+    { VKEY_9, EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN, '(' },
+    { VKEY_9, EF_SHIFT_DOWN | EF_CONTROL_DOWN, '\0' },
+
+    { VKEY_NUMPAD0, EF_CONTROL_DOWN, '\0' },
+    { VKEY_NUMPAD0, EF_SHIFT_DOWN, '0' },
+
+    { VKEY_NUMPAD9, EF_CONTROL_DOWN, '\0' },
+    { VKEY_NUMPAD9, EF_SHIFT_DOWN, '9' },
+
+    { VKEY_TAB, EF_CONTROL_DOWN, '\0' },
+    { VKEY_TAB, EF_SHIFT_DOWN, '\t' },
+
+    { VKEY_MULTIPLY, EF_CONTROL_DOWN, '\0' },
+    { VKEY_MULTIPLY, EF_SHIFT_DOWN, '*' },
+    { VKEY_ADD, EF_CONTROL_DOWN, '\0' },
+    { VKEY_ADD, EF_SHIFT_DOWN, '+' },
+    { VKEY_SUBTRACT, EF_CONTROL_DOWN, '\0' },
+    { VKEY_SUBTRACT, EF_SHIFT_DOWN, '-' },
+    { VKEY_DECIMAL, EF_CONTROL_DOWN, '\0' },
+    { VKEY_DECIMAL, EF_SHIFT_DOWN, '.' },
+    { VKEY_DIVIDE, EF_CONTROL_DOWN, '\0' },
+    { VKEY_DIVIDE, EF_SHIFT_DOWN, '/' },
+
+    { VKEY_OEM_1, EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_1, EF_SHIFT_DOWN, ':' },
+    { VKEY_OEM_PLUS, EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_PLUS, EF_SHIFT_DOWN, '+' },
+    { VKEY_OEM_COMMA, EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_COMMA, EF_SHIFT_DOWN, '<' },
+    { VKEY_OEM_PERIOD, EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_PERIOD, EF_SHIFT_DOWN, '>' },
+    { VKEY_OEM_3, EF_CONTROL_DOWN, '\0' },
+    { VKEY_OEM_3, EF_SHIFT_DOWN, '~' },
+  };
+
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestData); ++i) {
+    KeyEvent key(ET_KEY_PRESSED,
+                 kTestData[i].key_code,
+                 kTestData[i].flags);
+    EXPECT_EQ(kTestData[i].character, key.GetCharacter())
+        << " Index:" << i << " key_code:" << kTestData[i].key_code;
+  }
+}
+
+TEST(EventTest, KeyEventDirectUnicode) {
+  KeyEvent key(0x1234U, ui::VKEY_UNKNOWN, ui::EF_NONE);
+  EXPECT_EQ(0x1234U, key.GetCharacter());
+  EXPECT_EQ(ET_KEY_PRESSED, key.type());
+  EXPECT_TRUE(key.is_char());
+}
+
+TEST(EventTest, NormalizeKeyEventFlags) {
+#if defined(USE_X11)
+  // Normalize flags when KeyEvent is created from XEvent.
+  ScopedXI2Event event;
+  {
+    event.InitKeyEvent(ET_KEY_PRESSED, VKEY_SHIFT, EF_SHIFT_DOWN);
+    KeyEvent keyev(event);
+    EXPECT_EQ(EF_SHIFT_DOWN, keyev.flags());
+  }
+  {
+    event.InitKeyEvent(ET_KEY_RELEASED, VKEY_SHIFT, EF_SHIFT_DOWN);
+    KeyEvent keyev(event);
+    EXPECT_EQ(EF_NONE, keyev.flags());
+  }
+  {
+    event.InitKeyEvent(ET_KEY_PRESSED, VKEY_CONTROL, EF_CONTROL_DOWN);
+    KeyEvent keyev(event);
+    EXPECT_EQ(EF_CONTROL_DOWN, keyev.flags());
+  }
+  {
+    event.InitKeyEvent(ET_KEY_RELEASED, VKEY_CONTROL, EF_CONTROL_DOWN);
+    KeyEvent keyev(event);
+    EXPECT_EQ(EF_NONE, keyev.flags());
+  }
+  {
+    event.InitKeyEvent(ET_KEY_PRESSED, VKEY_MENU,  EF_ALT_DOWN);
+    KeyEvent keyev(event);
+    EXPECT_EQ(EF_ALT_DOWN, keyev.flags());
+  }
+  {
+    event.InitKeyEvent(ET_KEY_RELEASED, VKEY_MENU, EF_ALT_DOWN);
+    KeyEvent keyev(event);
+    EXPECT_EQ(EF_NONE, keyev.flags());
+  }
+#endif
+
+  // Do not normalize flags for synthesized events without
+  // KeyEvent::NormalizeFlags called explicitly.
+  {
+    KeyEvent keyev(ET_KEY_PRESSED, VKEY_SHIFT, EF_SHIFT_DOWN);
+    EXPECT_EQ(EF_SHIFT_DOWN, keyev.flags());
+  }
+  {
+    KeyEvent keyev(ET_KEY_RELEASED, VKEY_SHIFT, EF_SHIFT_DOWN);
+    EXPECT_EQ(EF_SHIFT_DOWN, keyev.flags());
+    keyev.NormalizeFlags();
+    EXPECT_EQ(EF_NONE, keyev.flags());
+  }
+  {
+    KeyEvent keyev(ET_KEY_PRESSED, VKEY_CONTROL, EF_CONTROL_DOWN);
+    EXPECT_EQ(EF_CONTROL_DOWN, keyev.flags());
+  }
+  {
+    KeyEvent keyev(ET_KEY_RELEASED, VKEY_CONTROL, EF_CONTROL_DOWN);
+    EXPECT_EQ(EF_CONTROL_DOWN, keyev.flags());
+    keyev.NormalizeFlags();
+    EXPECT_EQ(EF_NONE, keyev.flags());
+  }
+  {
+    KeyEvent keyev(ET_KEY_PRESSED, VKEY_MENU,  EF_ALT_DOWN);
+    EXPECT_EQ(EF_ALT_DOWN, keyev.flags());
+  }
+  {
+    KeyEvent keyev(ET_KEY_RELEASED, VKEY_MENU, EF_ALT_DOWN);
+    EXPECT_EQ(EF_ALT_DOWN, keyev.flags());
+    keyev.NormalizeFlags();
+    EXPECT_EQ(EF_NONE, keyev.flags());
+  }
+}
+
+TEST(EventTest, KeyEventCopy) {
+  KeyEvent key(ET_KEY_PRESSED, VKEY_A, EF_NONE);
+  scoped_ptr<KeyEvent> copied_key(new KeyEvent(key));
+  EXPECT_EQ(copied_key->type(), key.type());
+  EXPECT_EQ(copied_key->key_code(), key.key_code());
+}
+
+TEST(EventTest, KeyEventCode) {
+  const char kCodeForSpace[] = "Space";
+  const uint16 kNativeCodeSpace =
+      ui::KeycodeConverter::CodeToNativeKeycode(kCodeForSpace);
+  ASSERT_NE(ui::KeycodeConverter::InvalidNativeKeycode(), kNativeCodeSpace);
+
+  {
+    KeyEvent key(ET_KEY_PRESSED, VKEY_SPACE, kCodeForSpace, EF_NONE);
+    EXPECT_EQ(kCodeForSpace, key.code());
+  }
+  {
+    // Regardless the KeyEvent.key_code (VKEY_RETURN), code should be
+    // the specified value.
+    KeyEvent key(ET_KEY_PRESSED, VKEY_RETURN, kCodeForSpace, EF_NONE);
+    EXPECT_EQ(kCodeForSpace, key.code());
+  }
+  {
+    // If the synthetic event is initialized without code, it returns
+    // an empty string.
+    // TODO(komatsu): Fill a fallback value assuming the US keyboard layout.
+    KeyEvent key(ET_KEY_PRESSED, VKEY_SPACE, EF_NONE);
+    EXPECT_TRUE(key.code().empty());
+  }
+#if defined(USE_X11)
+  {
+    // KeyEvent converts from the native keycode (XKB) to the code.
+    ScopedXI2Event xevent;
+    xevent.InitKeyEvent(ET_KEY_PRESSED, VKEY_SPACE, kNativeCodeSpace);
+    KeyEvent key(xevent);
+    EXPECT_EQ(kCodeForSpace, key.code());
+  }
+#endif  // USE_X11
+#if defined(OS_WIN)
+  {
+    // Test a non extended key.
+    ASSERT_EQ((kNativeCodeSpace & 0xFF), kNativeCodeSpace);
+
+    const LPARAM lParam = GetLParamFromScanCode(kNativeCodeSpace);
+    MSG native_event = { NULL, WM_KEYUP, VKEY_SPACE, lParam };
+    KeyEvent key(native_event);
+
+    // KeyEvent converts from the native keycode (scan code) to the code.
+    EXPECT_EQ(kCodeForSpace, key.code());
+  }
+  {
+    const char kCodeForHome[]  = "Home";
+    const uint16 kNativeCodeHome  = 0xe047;
+
+    // 'Home' is an extended key with 0xe000 bits.
+    ASSERT_NE((kNativeCodeHome & 0xFF), kNativeCodeHome);
+    const LPARAM lParam = GetLParamFromScanCode(kNativeCodeHome);
+
+    MSG native_event = { NULL, WM_KEYUP, VKEY_HOME, lParam };
+    KeyEvent key(native_event);
+
+    // KeyEvent converts from the native keycode (scan code) to the code.
+    EXPECT_EQ(kCodeForHome, key.code());
+  }
+#endif  // OS_WIN
+}
+
+#if defined(USE_X11) || defined(OS_WIN)
+TEST(EventTest, AutoRepeat) {
+  const uint16 kNativeCodeA = ui::KeycodeConverter::CodeToNativeKeycode("KeyA");
+  const uint16 kNativeCodeB = ui::KeycodeConverter::CodeToNativeKeycode("KeyB");
+#if defined(USE_X11)
+  ScopedXI2Event native_event_a_pressed;
+  native_event_a_pressed.InitKeyEvent(ET_KEY_PRESSED, VKEY_A, kNativeCodeA);
+  ScopedXI2Event native_event_a_released;
+  native_event_a_released.InitKeyEvent(ET_KEY_RELEASED, VKEY_A, kNativeCodeA);
+  ScopedXI2Event native_event_b_pressed;
+  native_event_b_pressed.InitKeyEvent(ET_KEY_PRESSED, VKEY_B, kNativeCodeB);
+  ScopedXI2Event native_event_a_pressed_nonstandard_state;
+  native_event_a_pressed_nonstandard_state.InitKeyEvent(
+      ET_KEY_PRESSED, VKEY_A, kNativeCodeA);
+  // IBUS-GTK uses the mask (1 << 25) to detect reposted event.
+  static_cast<XEvent*>(native_event_a_pressed_nonstandard_state)->xkey.state |=
+      1 << 25;
+#elif defined(OS_WIN)
+  const LPARAM lParam_a = GetLParamFromScanCode(kNativeCodeA);
+  const LPARAM lParam_b = GetLParamFromScanCode(kNativeCodeB);
+  MSG native_event_a_pressed = { NULL, WM_KEYDOWN, VKEY_A, lParam_a };
+  MSG native_event_a_released = { NULL, WM_KEYUP, VKEY_A, lParam_a };
+  MSG native_event_b_pressed = { NULL, WM_KEYUP, VKEY_B, lParam_b };
+#endif
+  KeyEvent key_a1(native_event_a_pressed);
+  EXPECT_FALSE(key_a1.IsRepeat());
+  KeyEvent key_a1_released(native_event_a_released);
+  EXPECT_FALSE(key_a1_released.IsRepeat());
+
+  KeyEvent key_a2(native_event_a_pressed);
+  EXPECT_FALSE(key_a2.IsRepeat());
+  KeyEvent key_a2_repeated(native_event_a_pressed);
+  EXPECT_TRUE(key_a2_repeated.IsRepeat());
+  KeyEvent key_a2_released(native_event_a_released);
+  EXPECT_FALSE(key_a2_released.IsRepeat());
+
+  KeyEvent key_a3(native_event_a_pressed);
+  EXPECT_FALSE(key_a3.IsRepeat());
+  KeyEvent key_b(native_event_b_pressed);
+  EXPECT_FALSE(key_b.IsRepeat());
+  KeyEvent key_a3_again(native_event_a_pressed);
+  EXPECT_FALSE(key_a3_again.IsRepeat());
+  KeyEvent key_a3_repeated(native_event_a_pressed);
+  EXPECT_TRUE(key_a3_repeated.IsRepeat());
+  KeyEvent key_a3_repeated2(native_event_a_pressed);
+  EXPECT_TRUE(key_a3_repeated2.IsRepeat());
+  KeyEvent key_a3_released(native_event_a_released);
+  EXPECT_FALSE(key_a3_released.IsRepeat());
+
+#if defined(USE_X11)
+  KeyEvent key_a4_pressed(native_event_a_pressed);
+  EXPECT_FALSE(key_a4_pressed.IsRepeat());
+
+  KeyEvent key_a4_pressed_nonstandard_state(
+      native_event_a_pressed_nonstandard_state);
+  EXPECT_FALSE(key_a4_pressed_nonstandard_state.IsRepeat());
+#endif
+}
+#endif  // USE_X11 || OS_WIN
+
+}  // namespace ui
diff --git a/ui/events/event_utils.cc b/ui/events/event_utils.cc
new file mode 100644
index 0000000..2e3517e
--- /dev/null
+++ b/ui/events/event_utils.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 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 "ui/events/event_utils.h"
+
+#include <vector>
+
+#include "ui/events/event.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/screen.h"
+
+namespace ui {
+
+namespace {
+int g_custom_event_types = ET_LAST;
+}  // namespace
+
+scoped_ptr<Event> EventFromNative(const base::NativeEvent& native_event) {
+  scoped_ptr<Event> event;
+  EventType type = EventTypeFromNative(native_event);
+  switch(type) {
+    case ET_KEY_PRESSED:
+    case ET_KEY_RELEASED:
+      event.reset(new KeyEvent(native_event));
+      break;
+
+    case ET_TRANSLATED_KEY_PRESS:
+    case ET_TRANSLATED_KEY_RELEASE:
+      // These should not be generated by native events.
+      NOTREACHED();
+      break;
+
+    case ET_MOUSE_PRESSED:
+    case ET_MOUSE_DRAGGED:
+    case ET_MOUSE_RELEASED:
+    case ET_MOUSE_MOVED:
+    case ET_MOUSE_ENTERED:
+    case ET_MOUSE_EXITED:
+      event.reset(new MouseEvent(native_event));
+      break;
+
+    case ET_MOUSEWHEEL:
+      event.reset(new MouseWheelEvent(native_event));
+      break;
+
+    case ET_SCROLL_FLING_START:
+    case ET_SCROLL_FLING_CANCEL:
+    case ET_SCROLL:
+      event.reset(new ScrollEvent(native_event));
+      break;
+
+    case ET_TOUCH_RELEASED:
+    case ET_TOUCH_PRESSED:
+    case ET_TOUCH_MOVED:
+    case ET_TOUCH_CANCELLED:
+      event.reset(new TouchEvent(native_event));
+      break;
+
+    default:
+      break;
+  }
+  return event.Pass();
+}
+
+// From third_party/WebKit/Source/web/gtk/WebInputEventFactory.cpp:
+base::char16 GetControlCharacterForKeycode(int windows_key_code, bool shift) {
+  if (windows_key_code >= ui::VKEY_A &&
+    windows_key_code <= ui::VKEY_Z) {
+    // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
+    return windows_key_code - ui::VKEY_A + 1;
+  }
+  if (shift) {
+    // following graphics chars require shift key to input.
+    switch (windows_key_code) {
+      // ctrl-@ maps to \x00 (Null byte)
+      case ui::VKEY_2:
+        return 0;
+      // ctrl-^ maps to \x1E (Record separator, Information separator two)
+      case ui::VKEY_6:
+        return 0x1E;
+      // ctrl-_ maps to \x1F (Unit separator, Information separator one)
+      case ui::VKEY_OEM_MINUS:
+        return 0x1F;
+      // Returns 0 for all other keys to avoid inputting unexpected chars.
+      default:
+        break;
+    }
+  } else {
+    switch (windows_key_code) {
+      // ctrl-[ maps to \x1B (Escape)
+      case ui::VKEY_OEM_4:
+        return 0x1B;
+      // ctrl-\ maps to \x1C (File separator, Information separator four)
+      case ui::VKEY_OEM_5:
+        return 0x1C;
+      // ctrl-] maps to \x1D (Group separator, Information separator three)
+      case ui::VKEY_OEM_6:
+        return 0x1D;
+      // ctrl-Enter maps to \x0A (Line feed)
+      case ui::VKEY_RETURN:
+        return 0x0A;
+      // Returns 0 for all other keys to avoid inputting unexpected chars.
+      default:
+        break;
+    }
+  }
+  return 0;
+}
+
+int RegisterCustomEventType() {
+  return ++g_custom_event_types;
+}
+
+base::TimeDelta EventTimeForNow() {
+  return base::TimeDelta::FromInternalValue(
+      base::TimeTicks::Now().ToInternalValue());
+}
+
+bool ShouldDefaultToNaturalScroll() {
+  return GetInternalDisplayTouchSupport() ==
+      gfx::Display::TOUCH_SUPPORT_AVAILABLE;
+}
+
+gfx::Display::TouchSupport GetInternalDisplayTouchSupport() {
+  gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE);
+  // No screen in some unit tests.
+  if (!screen)
+    return gfx::Display::TOUCH_SUPPORT_UNKNOWN;
+  const std::vector<gfx::Display>& displays = screen->GetAllDisplays();
+  for (std::vector<gfx::Display>::const_iterator it = displays.begin();
+       it != displays.end(); ++it) {
+    if (it->IsInternal())
+      return it->touch_support();
+  }
+  return gfx::Display::TOUCH_SUPPORT_UNAVAILABLE;
+}
+
+}  // namespace ui
diff --git a/ui/events/event_utils.h b/ui/events/event_utils.h
new file mode 100644
index 0000000..f1f5f34
--- /dev/null
+++ b/ui/events/event_utils.h
@@ -0,0 +1,190 @@
+// Copyright (c) 2012 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 UI_EVENTS_EVENT_UTILS_H_
+#define UI_EVENTS_EVENT_UTILS_H_
+
+#include "base/basictypes.h"
+#include "base/event_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/string16.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/events/events_export.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#endif
+
+namespace gfx {
+class Point;
+class Vector2d;
+}
+
+namespace base {
+class TimeDelta;
+}
+
+namespace ui {
+
+class Event;
+class MouseEvent;
+
+// Updates the list of devices for cached properties.
+EVENTS_EXPORT void UpdateDeviceList();
+
+// Returns a ui::Event wrapping a native event. Ownership of the returned value
+// is transferred to the caller.
+EVENTS_EXPORT scoped_ptr<Event> EventFromNative(
+    const base::NativeEvent& native_event);
+
+// Get the EventType from a native event.
+EVENTS_EXPORT EventType EventTypeFromNative(
+    const base::NativeEvent& native_event);
+
+// Get the EventFlags from a native event.
+EVENTS_EXPORT int EventFlagsFromNative(const base::NativeEvent& native_event);
+
+// Get the timestamp from a native event.
+EVENTS_EXPORT base::TimeDelta EventTimeFromNative(
+    const base::NativeEvent& native_event);
+
+// Create a timestamp based on the current time.
+EVENTS_EXPORT base::TimeDelta EventTimeForNow();
+
+// Get the location from a native event.  The coordinate system of the resultant
+// |Point| has the origin at top-left of the "root window".  The nature of
+// this "root window" and how it maps to platform-specific drawing surfaces is
+// defined in ui/aura/root_window.* and ui/aura/window_tree_host*.
+// TODO(tdresser): Return gfx::PointF here. See crbug.com/337827.
+EVENTS_EXPORT gfx::Point EventLocationFromNative(
+    const base::NativeEvent& native_event);
+
+// Gets the location in native system coordinate space.
+EVENTS_EXPORT gfx::Point EventSystemLocationFromNative(
+    const base::NativeEvent& native_event);
+
+#if defined(USE_X11)
+// Returns the 'real' button for an event. The button reported in slave events
+// does not take into account any remapping (e.g. using xmodmap), while the
+// button reported in master events do. This is a utility function to always
+// return the mapped button.
+EVENTS_EXPORT int EventButtonFromNative(const base::NativeEvent& native_event);
+#endif
+
+// Returns the KeyboardCode from a native event.
+EVENTS_EXPORT KeyboardCode KeyboardCodeFromNative(
+    const base::NativeEvent& native_event);
+
+// Returns the DOM KeyboardEvent code (physical location in the
+// keyboard) from a native event.  The ownership of the return value
+// is NOT trasferred to the caller.
+EVENTS_EXPORT const char* CodeFromNative(
+    const base::NativeEvent& native_event);
+
+// Returns the platform related key code. For X11, it is xksym value.
+EVENTS_EXPORT uint32 PlatformKeycodeFromNative(
+    const base::NativeEvent& native_event);
+
+// Returns a control character sequences from a |windows_key_code|.
+EVENTS_EXPORT base::char16 GetControlCharacterForKeycode(int windows_key_code,
+                                                         bool shift);
+
+// Returns true if the keyboard event is a character event rather than
+// a keystroke event.
+EVENTS_EXPORT bool IsCharFromNative(const base::NativeEvent& native_event);
+
+// Returns the flags of the button that changed during a press/release.
+EVENTS_EXPORT int GetChangedMouseButtonFlagsFromNative(
+    const base::NativeEvent& native_event);
+
+// Gets the mouse wheel offsets from a native event.
+EVENTS_EXPORT gfx::Vector2d GetMouseWheelOffset(
+    const base::NativeEvent& native_event);
+
+// Returns a copy of |native_event|. Depending on the platform, this copy may
+// need to be deleted with ReleaseCopiedNativeEvent().
+base::NativeEvent CopyNativeEvent(
+    const base::NativeEvent& native_event);
+
+// Delete a |native_event| previously created by CopyNativeEvent().
+void ReleaseCopiedNativeEvent(
+    const base::NativeEvent& native_event);
+
+// Gets the touch id from a native event.
+EVENTS_EXPORT int GetTouchId(const base::NativeEvent& native_event);
+
+// Increases the number of times |ClearTouchIdIfReleased| needs to be called on
+// an event with a given touch id before it will actually be cleared.
+EVENTS_EXPORT void IncrementTouchIdRefCount(
+    const base::NativeEvent& native_event);
+
+// Clear the touch id from bookkeeping if it is a release/cancel event.
+EVENTS_EXPORT void ClearTouchIdIfReleased(
+    const base::NativeEvent& native_event);
+
+// Gets the radius along the X/Y axis from a native event. Default is 1.0.
+EVENTS_EXPORT float GetTouchRadiusX(const base::NativeEvent& native_event);
+EVENTS_EXPORT float GetTouchRadiusY(const base::NativeEvent& native_event);
+
+// Gets the angle of the major axis away from the X axis. Default is 0.0.
+EVENTS_EXPORT float GetTouchAngle(const base::NativeEvent& native_event);
+
+// Gets the force from a native_event. Normalized to be [0, 1]. Default is 0.0.
+EVENTS_EXPORT float GetTouchForce(const base::NativeEvent& native_event);
+
+// Gets the fling velocity from a native event. is_cancel is set to true if
+// this was a tap down, intended to stop an ongoing fling.
+EVENTS_EXPORT bool GetFlingData(const base::NativeEvent& native_event,
+                                float* vx,
+                                float* vy,
+                                float* vx_ordinal,
+                                float* vy_ordinal,
+                                bool* is_cancel);
+
+// Returns whether this is a scroll event and optionally gets the amount to be
+// scrolled. |x_offset|, |y_offset| and |finger_count| can be NULL.
+EVENTS_EXPORT bool GetScrollOffsets(const base::NativeEvent& native_event,
+                                    float* x_offset,
+                                    float* y_offset,
+                                    float* x_offset_ordinal,
+                                    float* y_offset_ordinal,
+                                    int* finger_count);
+
+// Returns whether natural scrolling should be used for touchpad.
+EVENTS_EXPORT bool ShouldDefaultToNaturalScroll();
+
+// Returns whether or not the internal display produces touch events.
+EVENTS_EXPORT gfx::Display::TouchSupport GetInternalDisplayTouchSupport();
+
+#if defined(OS_WIN)
+EVENTS_EXPORT int GetModifiersFromACCEL(const ACCEL& accel);
+EVENTS_EXPORT int GetModifiersFromKeyState();
+
+// Returns true if |message| identifies a mouse event that was generated as the
+// result of a touch event.
+EVENTS_EXPORT bool IsMouseEventFromTouch(UINT message);
+
+// Converts scan code and lParam each other.  The scan code
+// representing an extended key contains 0xE000 bits.
+EVENTS_EXPORT uint16 GetScanCodeFromLParam(LPARAM lParam);
+EVENTS_EXPORT LPARAM GetLParamFromScanCode(uint16 scan_code);
+
+#endif
+
+#if defined(USE_X11)
+// Update the native X11 event to correspond to the new flags.
+EVENTS_EXPORT void UpdateX11EventForFlags(Event* event);
+// Update the native X11 event to correspond to the new button flags.
+EVENTS_EXPORT void UpdateX11EventForChangedButtonFlags(MouseEvent* event);
+#endif
+
+// Registers a custom event type.
+EVENTS_EXPORT int RegisterCustomEventType();
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_EVENT_UTILS_H_
diff --git a/ui/events/events.gyp b/ui/events/events.gyp
new file mode 100644
index 0000000..40ccca6
--- /dev/null
+++ b/ui/events/events.gyp
@@ -0,0 +1,390 @@
+# Copyright 2013 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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [
+    {
+      # GN version: //ui/events:dom4_keycode_converter
+      'target_name': 'dom4_keycode_converter',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'keycodes/dom4/keycode_converter.cc',
+        'keycodes/dom4/keycode_converter.h',
+        'keycodes/dom4/keycode_converter_data.h',
+      ],
+    },
+    {
+      # GN version: //ui/events:events_base
+      'target_name': 'events_base',
+      'type': '<(component)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+        '<(DEPTH)/skia/skia.gyp:skia',
+        '../gfx/gfx.gyp:gfx',
+        '../gfx/gfx.gyp:gfx_geometry',
+        'dom4_keycode_converter',
+      ],
+      'defines': [
+        'EVENTS_BASE_IMPLEMENTATION',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'device_data_manager.cc',
+        'device_data_manager.h',
+        'device_hotplug_event_observer.h',
+        'event_constants.h',
+        'event_switches.cc',
+        'event_switches.h',
+        'events_base_export.h',
+        'gesture_event_details.cc',
+        'gesture_event_details.h',
+        'gestures/fling_curve.cc',
+        'gestures/fling_curve.h',
+        'gestures/gesture_configuration.cc',
+        'gestures/gesture_configuration.h',
+        'keycodes/keyboard_code_conversion.cc',
+        'keycodes/keyboard_code_conversion.h',
+        'keycodes/keyboard_code_conversion_android.cc',
+        'keycodes/keyboard_code_conversion_android.h',
+        'keycodes/keyboard_code_conversion_mac.h',
+        'keycodes/keyboard_code_conversion_mac.mm',
+        'keycodes/keyboard_code_conversion_win.cc',
+        'keycodes/keyboard_code_conversion_win.h',
+        'keycodes/keyboard_code_conversion_x.cc',
+        'keycodes/keyboard_code_conversion_x.h',
+        'keycodes/keyboard_codes.h',
+        'latency_info.cc',
+        'latency_info.h',
+        'touchscreen_device.cc',
+        'touchscreen_device.h',
+        'x/device_data_manager_x11.cc',
+        'x/device_data_manager_x11.h',
+        'x/device_list_cache_x.cc',
+        'x/device_list_cache_x.h',
+        'x/hotplug_event_handler_x11.cc',
+        'x/hotplug_event_handler_x11.h',
+        'x/keysym_to_unicode.cc',
+        'x/keysym_to_unicode.h',
+        'x/touch_factory_x11.cc',
+        'x/touch_factory_x11.h',
+      ],
+      'export_dependent_settings': [
+        '../../ui/gfx/gfx.gyp:gfx',
+      ],
+      'conditions': [
+        ['use_x11==1', {
+          'dependencies': [
+            '../../build/linux/system.gyp:x11',
+            '../gfx/x/gfx_x11.gyp:gfx_x11',
+          ],
+        }],
+      ],
+    },
+    {
+      # GN version: //ui/events
+      'target_name': 'events',
+      'type': '<(component)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+        '<(DEPTH)/skia/skia.gyp:skia',
+        '../gfx/gfx.gyp:gfx',
+        '../gfx/gfx.gyp:gfx_geometry',
+        'events_base',
+        'gesture_detection',
+      ],
+      'defines': [
+        'EVENTS_IMPLEMENTATION',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'cocoa/cocoa_event_utils.h',
+        'cocoa/cocoa_event_utils.mm',
+        'cocoa/events_mac.mm',
+        'event.cc',
+        'event.h',
+        'event_dispatcher.cc',
+        'event_dispatcher.h',
+        'event_handler.cc',
+        'event_handler.h',
+        'event_processor.cc',
+        'event_processor.h',
+        'event_rewriter.h',
+        'event_source.cc',
+        'event_source.h',
+        'event_target.cc',
+        'event_target.h',
+        'event_target_iterator.h',
+        'event_targeter.cc',
+        'event_targeter.h',
+        'event_utils.cc',
+        'event_utils.h',
+        'events_export.h',
+        'events_stub.cc',
+        'gestures/gesture_provider_aura.cc',
+        'gestures/gesture_provider_aura.h',
+        'gestures/gesture_recognizer.h',
+        'gestures/gesture_recognizer_impl.cc',
+        'gestures/gesture_recognizer_impl.h',
+        'gestures/gesture_recognizer_impl_mac.cc',
+        'gestures/gesture_types.h',
+        'gestures/motion_event_aura.cc',
+        'gestures/motion_event_aura.h',
+        'ozone/events_ozone.cc',
+        'win/events_win.cc',
+        'x/events_x.cc',
+        'linux/text_edit_command_auralinux.cc',
+        'linux/text_edit_command_auralinux.h',
+        'linux/text_edit_key_bindings_delegate_auralinux.cc',
+        'linux/text_edit_key_bindings_delegate_auralinux.h',
+      ],
+      'conditions': [
+        ['use_x11==1', {
+          'dependencies': [
+            '../../build/linux/system.gyp:x11',
+          ],
+        }],
+        ['use_aura==0', {
+          'sources!': [
+            'gestures/gesture_provider_aura.cc',
+            'gestures/gesture_provider_aura.h',
+            'gestures/gesture_recognizer.h',
+            'gestures/gesture_recognizer_impl.cc',
+            'gestures/gesture_recognizer_impl.h',
+            'gestures/gesture_types.h',
+            'gestures/motion_event_aura.cc',
+            'gestures/motion_event_aura.h',
+          ],
+        }],
+        # We explicitly enumerate the platforms we _do_ provide native cracking
+        # for here.
+        ['OS=="win" or OS=="mac" or use_x11==1 or use_ozone==1', {
+          'sources!': [
+            'events_stub.cc',
+          ],
+        }],
+        ['chromeos==1', {
+          'sources!': [
+            'linux/text_edit_command_auralinux.cc',
+            'linux/text_edit_command_auralinux.h',
+            'linux/text_edit_key_bindings_delegate_auralinux.cc',
+            'linux/text_edit_key_bindings_delegate_auralinux.h',
+          ],
+        }],
+      ],
+    },
+    {
+      # GN version: //ui/events:gesture_detection
+      'target_name': 'gesture_detection',
+      'type': '<(component)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+        '../gfx/gfx.gyp:gfx',
+        '../gfx/gfx.gyp:gfx_geometry',
+        'events_base',
+      ],
+      'defines': [
+        'GESTURE_DETECTION_IMPLEMENTATION',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'gesture_detection/bitset_32.h',
+        'gesture_detection/filtered_gesture_provider.cc',
+        'gesture_detection/filtered_gesture_provider.h',
+        'gesture_detection/gesture_config_helper.h',
+        'gesture_detection/gesture_config_helper_android.cc',
+        'gesture_detection/gesture_config_helper_aura.cc',
+        'gesture_detection/gesture_detection_export.h',
+        'gesture_detection/gesture_detector.cc',
+        'gesture_detection/gesture_detector.h',
+        'gesture_detection/gesture_event_data.cc',
+        'gesture_detection/gesture_event_data.h',
+        'gesture_detection/gesture_event_data_packet.cc',
+        'gesture_detection/gesture_event_data_packet.h',
+        'gesture_detection/gesture_listeners.cc',
+        'gesture_detection/gesture_listeners.h',
+        'gesture_detection/gesture_provider.cc',
+        'gesture_detection/gesture_provider.h',
+        'gesture_detection/gesture_touch_uma_histogram.cc',
+        'gesture_detection/gesture_touch_uma_histogram.h',
+        'gesture_detection/motion_event.cc',
+        'gesture_detection/motion_event.h',
+        'gesture_detection/motion_event_buffer.cc',
+        'gesture_detection/motion_event_buffer.h',
+        'gesture_detection/motion_event_generic.cc',
+        'gesture_detection/motion_event_generic.h',
+        'gesture_detection/scale_gesture_detector.cc',
+        'gesture_detection/scale_gesture_detector.h',
+        'gesture_detection/scale_gesture_listeners.cc',
+        'gesture_detection/scale_gesture_listeners.h',
+        'gesture_detection/snap_scroll_controller.cc',
+        'gesture_detection/snap_scroll_controller.h',
+        'gesture_detection/touch_disposition_gesture_filter.cc',
+        'gesture_detection/touch_disposition_gesture_filter.h',
+        'gesture_detection/velocity_tracker_state.cc',
+        'gesture_detection/velocity_tracker_state.h',
+        'gesture_detection/velocity_tracker.cc',
+        'gesture_detection/velocity_tracker.h',
+      ],
+      'conditions': [
+        ['use_aura!=1 and OS!="android"', {
+          'sources': [
+            'gesture_detection/gesture_config_helper.cc',
+          ],
+        }],
+      ],
+    },
+    {
+      # GN version: //ui/events:test_support
+      'target_name': 'events_test_support',
+      'type': 'static_library',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/skia/skia.gyp:skia',
+        '../gfx/gfx.gyp:gfx_geometry',
+        'events',
+        'events_base',
+        'gesture_detection',
+        'platform/events_platform.gyp:events_platform',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'test/cocoa_test_event_utils.h',
+        'test/cocoa_test_event_utils.mm',
+        'test/event_generator.cc',
+        'test/event_generator.h',
+        'test/events_test_utils.cc',
+        'test/events_test_utils.h',
+        'test/events_test_utils_x11.cc',
+        'test/events_test_utils_x11.h',
+        'test/mock_motion_event.cc',
+        'test/mock_motion_event.h',
+        'test/platform_event_waiter.cc',
+        'test/platform_event_waiter.h',
+        'test/test_event_handler.cc',
+        'test/test_event_handler.h',
+        'test/test_event_processor.cc',
+        'test/test_event_processor.h',
+        'test/test_event_target.cc',
+        'test/test_event_target.h',
+      ],
+      'conditions': [
+        ['OS=="ios"', {
+          # The cocoa files don't apply to iOS.
+          'sources/': [['exclude', 'cocoa']],
+        }],
+      ],
+    },
+    {
+      # GN version: //ui/events:events_unittests
+      'target_name': 'events_unittests',
+      'type': '<(gtest_target_type)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/base/base.gyp:run_all_unittests',
+        '<(DEPTH)/base/base.gyp:test_support_base',
+        '<(DEPTH)/skia/skia.gyp:skia',
+        '<(DEPTH)/testing/gtest.gyp:gtest',
+        '../gfx/gfx.gyp:gfx',
+        '../gfx/gfx.gyp:gfx_geometry',
+        '../gfx/gfx.gyp:gfx_test_support',
+        'dom4_keycode_converter',
+        'events',
+        'events_base',
+        'events_test_support',
+        'gesture_detection',
+        'platform/events_platform.gyp:events_platform',
+      ],
+      'sources': [
+        # Note: sources list duplicated in GN build.
+        'cocoa/events_mac_unittest.mm',
+        'event_dispatcher_unittest.cc',
+        'event_processor_unittest.cc',
+        'event_rewriter_unittest.cc',
+        'event_unittest.cc',
+        'gesture_detection/bitset_32_unittest.cc',
+        'gesture_detection/gesture_event_data_packet_unittest.cc',
+        'gesture_detection/gesture_provider_unittest.cc',
+        'gesture_detection/motion_event_buffer_unittest.cc',
+        'gesture_detection/motion_event_generic_unittest.cc',
+        'gesture_detection/touch_disposition_gesture_filter_unittest.cc',
+        'gesture_detection/velocity_tracker_unittest.cc',
+        'gestures/fling_curve_unittest.cc',
+        'gestures/gesture_provider_aura_unittest.cc',
+        'gestures/motion_event_aura_unittest.cc',
+        'keycodes/dom4/keycode_converter_unittest.cc',
+        'latency_info_unittest.cc',
+        'platform/platform_event_source_unittest.cc',
+        'x/events_x_unittest.cc',
+      ],
+      'conditions': [
+        ['use_x11==1', {
+          'dependencies': [
+            '../../build/linux/system.gyp:x11',
+            '../gfx/x/gfx_x11.gyp:gfx_x11',
+          ],
+        }],
+        ['use_ozone==1', {
+          'sources': [
+            'ozone/evdev/key_event_converter_evdev_unittest.cc',
+            'ozone/evdev/touch_event_converter_evdev_unittest.cc',
+          ],
+          'dependencies': [
+            'ozone/events_ozone.gyp:events_ozone',
+            'ozone/events_ozone.gyp:events_ozone_evdev',
+          ]
+        }],
+        ['use_aura==0', {
+          'sources!': [
+            'gestures/gesture_provider_aura_unittest.cc',
+            'gestures/motion_event_aura_unittest.cc',
+          ],
+        }],
+        ['OS=="linux" and use_allocator!="none"', {
+          'dependencies': [
+            '<(DEPTH)/base/allocator/allocator.gyp:allocator',
+          ],
+        }],
+        # Exclude tests that rely on event_utils.h for platforms that do not
+        # provide native cracking, i.e., platforms that use events_stub.cc.
+        ['OS!="win" and use_x11!=1 and use_ozone!=1', {
+          'sources!': [
+            'event_unittest.cc',
+          ],
+        }],
+        ['OS == "android"', {
+          'dependencies': [
+            '../../testing/android/native_test.gyp:native_test_native_code',
+          ],
+        }],
+      ],
+    },
+  ],
+  'conditions': [
+    ['OS == "android"', {
+      'targets': [
+        {
+          'target_name': 'events_unittests_apk',
+          'type': 'none',
+          'dependencies': [
+            'events_unittests',
+          ],
+          'variables': {
+            'test_suite_name': 'events_unittests',
+          },
+          'includes': [ '../../build/apk_test.gypi' ],
+        },
+      ],
+    }],
+  ],
+}
diff --git a/ui/events/events_base_export.h b/ui/events/events_base_export.h
new file mode 100644
index 0000000..2da4f78
--- /dev/null
+++ b/ui/events/events_base_export.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 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 UI_EVENTS_EVENTS_BASE_EXPORT_H_
+#define UI_EVENTS_EVENTS_BASE_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(EVENTS_BASE_IMPLEMENTATION)
+#define EVENTS_BASE_EXPORT __declspec(dllexport)
+#else
+#define EVENTS_BASE_EXPORT __declspec(dllimport)
+#endif  // defined(EVENTS_BASE_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(EVENTS_BASE_IMPLEMENTATION)
+#define EVENTS_BASE_EXPORT __attribute__((visibility("default")))
+#else
+#define EVENTS_BASE_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define EVENTS_BASE_EXPORT
+#endif
+
+#endif  // UI_EVENTS_EVENTS_BASE_EXPORT_H_
diff --git a/ui/events/events_export.h b/ui/events/events_export.h
new file mode 100644
index 0000000..65ed7a2
--- /dev/null
+++ b/ui/events/events_export.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2013 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 UI_EVENTS_EVENTS_EXPORT_H_
+#define UI_EVENTS_EVENTS_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(EVENTS_IMPLEMENTATION)
+#define EVENTS_EXPORT __declspec(dllexport)
+#else
+#define EVENTS_EXPORT __declspec(dllimport)
+#endif  // defined(EVENTS_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(EVENTS_IMPLEMENTATION)
+#define EVENTS_EXPORT __attribute__((visibility("default")))
+#else
+#define EVENTS_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define EVENTS_EXPORT
+#endif
+
+#endif  // UI_EVENTS_EVENTS_EXPORT_H_
diff --git a/ui/events/events_stub.cc b/ui/events/events_stub.cc
new file mode 100644
index 0000000..5c83974
--- /dev/null
+++ b/ui/events/events_stub.cc
@@ -0,0 +1,161 @@
+// Copyright (c) 2013 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 "base/logging.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "ui/events/event_utils.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/vector2d.h"
+
+namespace ui {
+
+// Stub implementations of platform-specific methods in events_util.h, built
+// on platforms that currently do not have a complete implementation of events.
+
+void UpdateDeviceList() {
+  NOTIMPLEMENTED();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return base::TimeDelta();
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return gfx::Point();
+}
+
+gfx::Point EventSystemLocationFromNative(
+    const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return gfx::Point();
+}
+
+int EventButtonFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+    const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return gfx::Vector2d();
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+  NOTIMPLEMENTED() <<
+      "Don't know how to copy base::NativeEvent for this platform";
+  return NULL;
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+}
+
+int GetTouchId(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+                      float* x_offset,
+                      float* y_offset,
+                      float* x_offset_ordinal,
+                      float* y_offset_ordinal,
+                      int* finger_count) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+                  float* vx,
+                  float* vy,
+                  float* vx_ordinal,
+                  float* vy_ordinal,
+                  bool* is_cancel) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return static_cast<KeyboardCode>(0);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return "";
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+uint32 WindowsKeycodeFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+uint16 TextFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+uint16 UnmodifiedTextFromNative(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/OWNERS b/ui/events/gesture_detection/OWNERS
new file mode 100644
index 0000000..3e9a729
--- /dev/null
+++ b/ui/events/gesture_detection/OWNERS
@@ -0,0 +1,2 @@
+jdduke@chromium.org
+tdresser@chromium.org
diff --git a/ui/events/gesture_detection/bitset_32.h b/ui/events/gesture_detection/bitset_32.h
new file mode 100644
index 0000000..1cc2dad
--- /dev/null
+++ b/ui/events/gesture_detection/bitset_32.h
@@ -0,0 +1,133 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_BITSET_32_H_
+#define UI_EVENTS_GESTURE_DETECTION_BITSET_32_H_
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+
+namespace ui {
+
+// Port of BitSet32 from Android
+// * platform/system/core/include/utils/BitSet.h
+// * Change-Id: I9bbf41f9d2d4a2593b0e6d7d8be7e283f985bade
+// * Please update the Change-Id as upstream Android changes are pulled.
+struct BitSet32 {
+  uint32_t value;
+
+  inline BitSet32() : value(0) {}
+  explicit inline BitSet32(uint32_t value) : value(value) {}
+
+  // Gets the value associated with a particular bit index.
+  static inline uint32_t value_for_bit(uint32_t n) {
+    DCHECK_LE(n, 31U);
+    return 0x80000000 >> n;
+  }
+
+  // Clears the bit set.
+  inline void clear() { value = 0; }
+
+  // Returns the number of marked bits in the set.
+  inline uint32_t count() const { return popcnt(value); }
+
+  // Returns true if the bit set does not contain any marked bits.
+  inline bool is_empty() const { return !value; }
+
+  // Returns true if the bit set does not contain any unmarked bits.
+  inline bool is_full() const { return value == 0xffffffff; }
+
+  // Returns true if the specified bit is marked.
+  inline bool has_bit(uint32_t n) const {
+    return (value & value_for_bit(n)) != 0;
+  }
+
+  // Marks the specified bit.
+  inline void mark_bit(uint32_t n) { value |= value_for_bit(n); }
+
+  // Clears the specified bit.
+  inline void clear_bit(uint32_t n) { value &= ~value_for_bit(n); }
+
+  // Finds the first marked bit in the set.
+  // Result is undefined if all bits are unmarked.
+  inline uint32_t first_marked_bit() const { return clz(value); }
+
+  // Finds the first unmarked bit in the set.
+  // Result is undefined if all bits are marked.
+  inline uint32_t first_unmarked_bit() const { return clz(~value); }
+
+  // Finds the last marked bit in the set.
+  // Result is undefined if all bits are unmarked.
+  inline uint32_t last_marked_bit() const { return 31 - ctz(value); }
+
+  // Finds the first marked bit in the set and clears it.  Returns the bit
+  // index.
+  // Result is undefined if all bits are unmarked.
+  inline uint32_t clear_first_marked_bit() {
+    uint32_t n = first_marked_bit();
+    clear_bit(n);
+    return n;
+  }
+
+  // Finds the first unmarked bit in the set and marks it.  Returns the bit
+  // index.
+  // Result is undefined if all bits are marked.
+  inline uint32_t mark_first_unmarked_bit() {
+    uint32_t n = first_unmarked_bit();
+    mark_bit(n);
+    return n;
+  }
+
+  // Finds the last marked bit in the set and clears it.  Returns the bit index.
+  // Result is undefined if all bits are unmarked.
+  inline uint32_t clear_last_marked_bit() {
+    uint32_t n = last_marked_bit();
+    clear_bit(n);
+    return n;
+  }
+
+  // Gets the inde of the specified bit in the set, which is the number of
+  // marked bits that appear before the specified bit.
+  inline uint32_t get_index_of_bit(uint32_t n) const {
+    DCHECK_LE(n, 31U);
+    return popcnt(value & ~(0xffffffffUL >> n));
+  }
+
+  inline bool operator==(const BitSet32& other) const {
+    return value == other.value;
+  }
+  inline bool operator!=(const BitSet32& other) const {
+    return value != other.value;
+  }
+
+ private:
+#if defined(COMPILER_GCC) || defined(__clang__)
+  static inline uint32_t popcnt(uint32_t v) { return __builtin_popcount(v); }
+  static inline uint32_t clz(uint32_t v) { return __builtin_clz(v); }
+  static inline uint32_t ctz(uint32_t v) { return __builtin_ctz(v); }
+#else
+  // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel
+  static inline uint32_t popcnt(uint32_t v) {
+    v = v - ((v >> 1) & 0x55555555);
+    v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
+    return (((v + (v >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
+  }
+  // TODO(jdduke): Use intrinsics (BitScan{Forward,Reverse}) with MSVC.
+  static inline uint32_t clz(uint32_t v) {
+    v |= (v >> 1);
+    v |= (v >> 2);
+    v |= (v >> 4);
+    v |= (v >> 8);
+    v |= (v >> 16);
+    return 32 - popcnt(v);
+  }
+  static inline uint32_t ctz(uint32_t v) {
+    return popcnt((v & static_cast<uint32_t>(-static_cast<int>(v))) - 1);
+  }
+#endif
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_BITSET_32_H_
diff --git a/ui/events/gesture_detection/bitset_32_unittest.cc b/ui/events/gesture_detection/bitset_32_unittest.cc
new file mode 100644
index 0000000..2e5d271
--- /dev/null
+++ b/ui/events/gesture_detection/bitset_32_unittest.cc
@@ -0,0 +1,100 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/bitset_32.h"
+
+namespace ui {
+
+class BitSet32Test : public testing::Test {};
+
+TEST_F(BitSet32Test, Basic) {
+  BitSet32 bits;
+
+  // Test the empty set.
+  EXPECT_EQ(0U, bits.count());
+  EXPECT_TRUE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_FALSE(bits.has_bit(0));
+  EXPECT_FALSE(bits.has_bit(31));
+
+  // Mark the first bit.
+  bits.mark_bit(0);
+  EXPECT_EQ(1U, bits.count());
+  EXPECT_FALSE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_TRUE(bits.has_bit(0));
+  EXPECT_FALSE(bits.has_bit(31));
+  EXPECT_EQ(0U, bits.first_marked_bit());
+  EXPECT_EQ(0U, bits.last_marked_bit());
+  EXPECT_EQ(1U, bits.first_unmarked_bit());
+
+  // Mark the last bit.
+  bits.mark_bit(31);
+  EXPECT_EQ(2U, bits.count());
+  EXPECT_FALSE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_TRUE(bits.has_bit(0));
+  EXPECT_TRUE(bits.has_bit(31));
+  EXPECT_FALSE(bits.has_bit(15));
+  EXPECT_EQ(0U, bits.first_marked_bit());
+  EXPECT_EQ(31U, bits.last_marked_bit());
+  EXPECT_EQ(1U, bits.first_unmarked_bit());
+  EXPECT_EQ(0U, bits.get_index_of_bit(0));
+  EXPECT_EQ(1U, bits.get_index_of_bit(1));
+  EXPECT_EQ(1U, bits.get_index_of_bit(2));
+  EXPECT_EQ(1U, bits.get_index_of_bit(31));
+
+  // Clear the first bit.
+  bits.clear_first_marked_bit();
+  EXPECT_EQ(1U, bits.count());
+  EXPECT_FALSE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_FALSE(bits.has_bit(0));
+  EXPECT_TRUE(bits.has_bit(31));
+  EXPECT_EQ(31U, bits.first_marked_bit());
+  EXPECT_EQ(31U, bits.last_marked_bit());
+  EXPECT_EQ(0U, bits.first_unmarked_bit());
+  EXPECT_EQ(0U, bits.get_index_of_bit(0));
+  EXPECT_EQ(0U, bits.get_index_of_bit(1));
+  EXPECT_EQ(0U, bits.get_index_of_bit(31));
+
+  // Clear the last bit (the set should be empty).
+  bits.clear_last_marked_bit();
+  EXPECT_EQ(0U, bits.count());
+  EXPECT_TRUE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_FALSE(bits.has_bit(0));
+  EXPECT_FALSE(bits.has_bit(31));
+  EXPECT_EQ(0U, bits.get_index_of_bit(0));
+  EXPECT_EQ(0U, bits.get_index_of_bit(31));
+  EXPECT_EQ(BitSet32(), bits);
+
+  // Mark the first unmarked bit (bit 0).
+  bits.mark_first_unmarked_bit();
+  EXPECT_EQ(1U, bits.count());
+  EXPECT_FALSE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_TRUE(bits.has_bit(0));
+  EXPECT_EQ(0U, bits.first_marked_bit());
+  EXPECT_EQ(0U, bits.last_marked_bit());
+  EXPECT_EQ(1U, bits.first_unmarked_bit());
+
+  // Mark the next unmarked bit (bit 1).
+  bits.mark_first_unmarked_bit();
+  EXPECT_EQ(2U, bits.count());
+  EXPECT_FALSE(bits.is_empty());
+  EXPECT_FALSE(bits.is_full());
+  EXPECT_TRUE(bits.has_bit(0));
+  EXPECT_TRUE(bits.has_bit(1));
+  EXPECT_EQ(0U, bits.first_marked_bit());
+  EXPECT_EQ(1U, bits.last_marked_bit());
+  EXPECT_EQ(2U, bits.first_unmarked_bit());
+  EXPECT_EQ(0U, bits.get_index_of_bit(0));
+  EXPECT_EQ(1U, bits.get_index_of_bit(1));
+  EXPECT_EQ(2U, bits.get_index_of_bit(2));
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/filtered_gesture_provider.cc b/ui/events/gesture_detection/filtered_gesture_provider.cc
new file mode 100644
index 0000000..76ee520
--- /dev/null
+++ b/ui/events/gesture_detection/filtered_gesture_provider.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 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 "ui/events/gesture_detection/filtered_gesture_provider.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+
+FilteredGestureProvider::FilteredGestureProvider(
+    const GestureProvider::Config& config,
+    GestureProviderClient* client)
+    : client_(client),
+      gesture_provider_(config, this),
+      gesture_filter_(this),
+      handling_event_(false) {}
+
+bool FilteredGestureProvider::OnTouchEvent(const MotionEvent& event) {
+  DCHECK(!handling_event_);
+  base::AutoReset<bool> handling_event(&handling_event_, true);
+
+  pending_gesture_packet_ = GestureEventDataPacket::FromTouch(event);
+
+  if (!gesture_provider_.OnTouchEvent(event))
+    return false;
+
+  TouchDispositionGestureFilter::PacketResult result =
+      gesture_filter_.OnGesturePacket(pending_gesture_packet_);
+  if (result != TouchDispositionGestureFilter::SUCCESS) {
+    NOTREACHED() << "Invalid touch gesture sequence detected.";
+    return false;
+  }
+
+  return true;
+}
+
+void FilteredGestureProvider::OnTouchEventAck(bool event_consumed) {
+  gesture_filter_.OnTouchEventAck(event_consumed);
+}
+
+void FilteredGestureProvider::SetMultiTouchZoomSupportEnabled(
+    bool enabled) {
+  gesture_provider_.SetMultiTouchZoomSupportEnabled(enabled);
+}
+
+void FilteredGestureProvider::SetDoubleTapSupportForPlatformEnabled(
+    bool enabled) {
+  gesture_provider_.SetDoubleTapSupportForPlatformEnabled(enabled);
+}
+
+void FilteredGestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled) {
+  gesture_provider_.SetDoubleTapSupportForPageEnabled(enabled);
+}
+
+const ui::MotionEvent* FilteredGestureProvider::GetCurrentDownEvent() const {
+  return gesture_provider_.current_down_event();
+}
+
+void FilteredGestureProvider::OnGestureEvent(const GestureEventData& event) {
+  if (handling_event_) {
+    pending_gesture_packet_.Push(event);
+    return;
+  }
+
+  gesture_filter_.OnGesturePacket(
+      GestureEventDataPacket::FromTouchTimeout(event));
+}
+
+void FilteredGestureProvider::ForwardGestureEvent(
+    const GestureEventData& event) {
+  client_->OnGestureEvent(event);
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/filtered_gesture_provider.h b/ui/events/gesture_detection/filtered_gesture_provider.h
new file mode 100644
index 0000000..4d63be6
--- /dev/null
+++ b/ui/events/gesture_detection/filtered_gesture_provider.h
@@ -0,0 +1,61 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_FILTERED_GESTURE_PROVIDER_H_
+#define UI_EVENTS_GESTURE_DETECTION_FILTERED_GESTURE_PROVIDER_H_
+
+#include "base/basictypes.h"
+#include "ui/events/gesture_detection/gesture_event_data_packet.h"
+#include "ui/events/gesture_detection/gesture_provider.h"
+#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h"
+
+namespace ui {
+
+// Provides filtered gesture detection and dispatch given a sequence of touch
+// events and touch event acks.
+class GESTURE_DETECTION_EXPORT FilteredGestureProvider
+    : public ui::TouchDispositionGestureFilterClient,
+      public ui::GestureProviderClient {
+ public:
+  // |client| will be offered all gestures detected by the |gesture_provider_|
+  // and allowed by the |gesture_filter_|.
+  FilteredGestureProvider(const GestureProvider::Config& config,
+                          GestureProviderClient* client);
+
+  // Returns true if |event| was both valid and successfully handled by the
+  // gesture provider.  Otherwise, returns false, in which case the caller
+  // should drop |event|, not forwarding it to the renderer.
+  bool OnTouchEvent(const MotionEvent& event);
+
+  // To be called upon ack of an event that was forwarded after a successful
+  // call to |OnTouchEvent()|.
+  void OnTouchEventAck(bool event_consumed);
+
+  // Methods delegated to |gesture_provider_|.
+  void SetMultiTouchZoomSupportEnabled(bool enabled);
+  void SetDoubleTapSupportForPlatformEnabled(bool enabled);
+  void SetDoubleTapSupportForPageEnabled(bool enabled);
+  const ui::MotionEvent* GetCurrentDownEvent() const;
+
+ private:
+  // GestureProviderClient implementation.
+  virtual void OnGestureEvent(const ui::GestureEventData& event) OVERRIDE;
+
+  // TouchDispositionGestureFilterClient implementation.
+  virtual void ForwardGestureEvent(const ui::GestureEventData& event) OVERRIDE;
+
+  GestureProviderClient* const client_;
+
+  ui::GestureProvider gesture_provider_;
+  ui::TouchDispositionGestureFilter gesture_filter_;
+
+  bool handling_event_;
+  ui::GestureEventDataPacket pending_gesture_packet_;
+
+  DISALLOW_COPY_AND_ASSIGN(FilteredGestureProvider);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_FILTERED_GESTURE_PROVIDER_H_
diff --git a/ui/events/gesture_detection/gesture_config_helper.cc b/ui/events/gesture_detection/gesture_config_helper.cc
new file mode 100644
index 0000000..0039e2c
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_config_helper.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_config_helper.h"
+
+#include "ui/gfx/screen.h"
+
+namespace ui {
+
+GestureProvider::Config DefaultGestureProviderConfig() {
+  GestureProvider::Config config;
+  config.display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
+  return config;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_config_helper.h b/ui/events/gesture_detection/gesture_config_helper.h
new file mode 100644
index 0000000..0b54c36
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_config_helper.h
@@ -0,0 +1,20 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_CONFIG_HELPER_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_CONFIG_HELPER_H_
+
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_detector.h"
+#include "ui/events/gesture_detection/gesture_provider.h"
+#include "ui/events/gesture_detection/scale_gesture_detector.h"
+
+namespace ui {
+
+GESTURE_DETECTION_EXPORT GestureProvider::Config
+DefaultGestureProviderConfig();
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_CONFIG_HELPER_H_
diff --git a/ui/events/gesture_detection/gesture_config_helper_android.cc b/ui/events/gesture_detection/gesture_config_helper_android.cc
new file mode 100644
index 0000000..0b23346
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_config_helper_android.cc
@@ -0,0 +1,77 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_config_helper.h"
+
+#include "ui/gfx/android/view_configuration.h"
+#include "ui/gfx/screen.h"
+
+using gfx::ViewConfiguration;
+
+namespace ui {
+namespace {
+// TODO(jdduke): Adopt GestureConfiguration on Android, crbug/339203.
+
+// This was the minimum tap/press size used on Android before the new gesture
+// detection pipeline.
+const float kMinGestureBoundsLengthDips = 24.f;
+
+// This value is somewhat arbitrary, but provides a reasonable maximum
+// approximating a large thumb depression.
+const float kMaxGestureBoundsLengthDips = kMinGestureBoundsLengthDips * 4.f;
+
+GestureDetector::Config DefaultGestureDetectorConfig(
+    const gfx::Display& display) {
+  GestureDetector::Config config;
+
+  config.longpress_timeout = base::TimeDelta::FromMilliseconds(
+      ViewConfiguration::GetLongPressTimeoutInMs());
+  config.showpress_timeout =
+      base::TimeDelta::FromMilliseconds(ViewConfiguration::GetTapTimeoutInMs());
+  config.double_tap_timeout = base::TimeDelta::FromMilliseconds(
+      ViewConfiguration::GetDoubleTapTimeoutInMs());
+
+  const float px_to_dp = 1.f / display.device_scale_factor();
+  config.touch_slop =
+      ViewConfiguration::GetTouchSlopInPixels() * px_to_dp;
+  config.double_tap_slop =
+      ViewConfiguration::GetDoubleTapSlopInPixels() * px_to_dp;
+  config.minimum_fling_velocity =
+      ViewConfiguration::GetMinimumFlingVelocityInPixelsPerSecond() * px_to_dp;
+  config.maximum_fling_velocity =
+      ViewConfiguration::GetMaximumFlingVelocityInPixelsPerSecond() * px_to_dp;
+
+  return config;
+}
+
+ScaleGestureDetector::Config DefaultScaleGestureDetectorConfig(
+    const gfx::Display& display) {
+  ScaleGestureDetector::Config config;
+
+  const float px_to_dp = 1.f / display.device_scale_factor();
+  config.span_slop = ViewConfiguration::GetTouchSlopInPixels() * 2.f * px_to_dp;
+  config.min_scaling_touch_major =
+      ViewConfiguration::GetMinScalingTouchMajorInPixels() * px_to_dp;
+  config.min_scaling_span =
+      ViewConfiguration::GetMinScalingSpanInPixels() * px_to_dp;
+  config.min_pinch_update_span_delta = 0.f;
+
+  return config;
+}
+
+}  // namespace
+
+GestureProvider::Config DefaultGestureProviderConfig() {
+  GestureProvider::Config config;
+  config.display = gfx::Screen::GetNativeScreen()->GetPrimaryDisplay();
+  config.gesture_detector_config = DefaultGestureDetectorConfig(config.display);
+  config.scale_gesture_detector_config =
+      DefaultScaleGestureDetectorConfig(config.display);
+  config.gesture_begin_end_types_enabled = false;
+  config.min_gesture_bounds_length = kMinGestureBoundsLengthDips;
+  config.max_gesture_bounds_length = kMaxGestureBoundsLengthDips;
+  return config;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_config_helper_aura.cc b/ui/events/gesture_detection/gesture_config_helper_aura.cc
new file mode 100644
index 0000000..94469c5
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_config_helper_aura.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_config_helper.h"
+
+#include <cmath>
+
+#include "base/command_line.h"
+#include "ui/events/event_switches.h"
+#include "ui/events/gestures/gesture_configuration.h"
+#include "ui/gfx/screen.h"
+
+namespace ui {
+namespace {
+
+GestureDetector::Config DefaultGestureDetectorConfig() {
+  GestureDetector::Config config;
+
+  config.longpress_timeout = base::TimeDelta::FromMilliseconds(
+      GestureConfiguration::long_press_time_in_seconds() * 1000.);
+  config.showpress_timeout = base::TimeDelta::FromMilliseconds(
+      GestureConfiguration::show_press_delay_in_ms());
+  config.double_tap_timeout = base::TimeDelta::FromMilliseconds(
+      GestureConfiguration::semi_long_press_time_in_seconds() * 1000.);
+  config.touch_slop =
+      GestureConfiguration::max_touch_move_in_pixels_for_click();
+  config.double_tap_slop =
+      GestureConfiguration::max_distance_between_taps_for_double_tap();
+  config.minimum_fling_velocity =
+      GestureConfiguration::min_scroll_velocity();
+  config.maximum_fling_velocity = GestureConfiguration::fling_velocity_cap();
+  config.swipe_enabled = true;
+  config.minimum_swipe_velocity = GestureConfiguration::min_swipe_speed();
+  config.maximum_swipe_deviation_angle =
+      GestureConfiguration::max_swipe_deviation_angle();
+  config.two_finger_tap_enabled = true;
+  config.two_finger_tap_max_separation =
+      GestureConfiguration::max_distance_for_two_finger_tap_in_pixels();
+  config.two_finger_tap_timeout = base::TimeDelta::FromMilliseconds(
+      GestureConfiguration::max_touch_down_duration_in_seconds_for_click() *
+      1000.);
+
+  return config;
+}
+
+ScaleGestureDetector::Config DefaultScaleGestureDetectorConfig() {
+  ScaleGestureDetector::Config config;
+  double min_pinch_update_distance =
+      CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kCompensateForUnstablePinchZoom)
+          ? GestureConfiguration::min_pinch_update_distance_in_pixels()
+          : 0;
+
+  config.span_slop =
+      GestureConfiguration::max_touch_move_in_pixels_for_click() * 2;
+  config.min_scaling_touch_major = GestureConfiguration::default_radius() * 2;
+  config.min_scaling_span = GestureConfiguration::min_scaling_span_in_pixels();
+  config.min_pinch_update_span_delta = min_pinch_update_distance;
+  return config;
+}
+
+}  // namespace
+
+GestureProvider::Config DefaultGestureProviderConfig() {
+  GestureProvider::Config config;
+  gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE);
+  // |screen| is sometimes NULL during tests.
+  if (screen)
+    config.display = screen->GetPrimaryDisplay();
+  config.gesture_detector_config = DefaultGestureDetectorConfig();
+  config.scale_gesture_detector_config = DefaultScaleGestureDetectorConfig();
+  config.gesture_begin_end_types_enabled = true;
+  // Half the size of the default touch length is a reasonable minimum.
+  config.min_gesture_bounds_length = GestureConfiguration::default_radius();
+  return config;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_detection_export.h b/ui/events/gesture_detection/gesture_detection_export.h
new file mode 100644
index 0000000..edef524
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_detection_export.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTION_EXPORT_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTION_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(GESTURE_DETECTION_IMPLEMENTATION)
+#define GESTURE_DETECTION_EXPORT __declspec(dllexport)
+#else
+#define GESTURE_DETECTION_EXPORT __declspec(dllimport)
+#endif  // defined(GESTURES_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(GESTURE_DETECTION_IMPLEMENTATION)
+#define GESTURE_DETECTION_EXPORT __attribute__((visibility("default")))
+#else
+#define GESTURE_DETECTION_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define GESTURE_DETECTION_EXPORT
+#endif
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTION_EXPORT_H_
diff --git a/ui/events/gesture_detection/gesture_detector.cc b/ui/events/gesture_detection/gesture_detector.cc
new file mode 100644
index 0000000..c092448
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_detector.cc
@@ -0,0 +1,500 @@
+// Copyright 2014 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.
+
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
+#include "ui/events/gesture_detection/gesture_detector.h"
+
+#include <cmath>
+
+#include "base/timer/timer.h"
+#include "ui/events/gesture_detection/gesture_listeners.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+namespace {
+
+// Using a small epsilon when comparing slop distances allows pixel perfect
+// slop determination when using fractional DIP coordinates (assuming the slop
+// region and DPI scale are reasonably proportioned).
+const float kSlopEpsilon = .05f;
+
+// Minimum distance a scroll must have traveled from the last scroll/focal point
+// to trigger an |OnScroll| callback.
+const float kScrollEpsilon = .1f;
+
+const float kDegreesToRadians = static_cast<float>(M_PI) / 180.0f;
+
+// Constants used by TimeoutGestureHandler.
+enum TimeoutEvent {
+  SHOW_PRESS = 0,
+  LONG_PRESS,
+  TAP,
+  TIMEOUT_EVENT_COUNT
+};
+
+}  // namespace
+
+// Note: These constants were taken directly from the default (unscaled)
+// versions found in Android's ViewConfiguration.
+GestureDetector::Config::Config()
+    : longpress_timeout(base::TimeDelta::FromMilliseconds(500)),
+      showpress_timeout(base::TimeDelta::FromMilliseconds(180)),
+      double_tap_timeout(base::TimeDelta::FromMilliseconds(300)),
+      double_tap_min_time(base::TimeDelta::FromMilliseconds(40)),
+      touch_slop(8),
+      double_tap_slop(100),
+      minimum_fling_velocity(50),
+      maximum_fling_velocity(8000),
+      swipe_enabled(false),
+      minimum_swipe_velocity(20),
+      maximum_swipe_deviation_angle(20.f),
+      two_finger_tap_enabled(false),
+      two_finger_tap_max_separation(300),
+      two_finger_tap_timeout(base::TimeDelta::FromMilliseconds(700)) {
+}
+
+GestureDetector::Config::~Config() {}
+
+class GestureDetector::TimeoutGestureHandler {
+ public:
+  TimeoutGestureHandler(const Config& config, GestureDetector* gesture_detector)
+      : gesture_detector_(gesture_detector) {
+    DCHECK(config.showpress_timeout <= config.longpress_timeout);
+
+    timeout_callbacks_[SHOW_PRESS] = &GestureDetector::OnShowPressTimeout;
+    timeout_delays_[SHOW_PRESS] = config.showpress_timeout;
+
+    timeout_callbacks_[LONG_PRESS] = &GestureDetector::OnLongPressTimeout;
+    timeout_delays_[LONG_PRESS] =
+        config.longpress_timeout + config.showpress_timeout;
+
+    timeout_callbacks_[TAP] = &GestureDetector::OnTapTimeout;
+    timeout_delays_[TAP] = config.double_tap_timeout;
+  }
+
+  ~TimeoutGestureHandler() {
+    Stop();
+  }
+
+  void StartTimeout(TimeoutEvent event) {
+    timeout_timers_[event].Start(FROM_HERE,
+                                 timeout_delays_[event],
+                                 gesture_detector_,
+                                 timeout_callbacks_[event]);
+  }
+
+  void StopTimeout(TimeoutEvent event) { timeout_timers_[event].Stop(); }
+
+  void Stop() {
+    for (size_t i = SHOW_PRESS; i < TIMEOUT_EVENT_COUNT; ++i)
+      timeout_timers_[i].Stop();
+  }
+
+  bool HasTimeout(TimeoutEvent event) const {
+    return timeout_timers_[event].IsRunning();
+  }
+
+ private:
+  typedef void (GestureDetector::*ReceiverMethod)();
+
+  GestureDetector* const gesture_detector_;
+  base::OneShotTimer<GestureDetector> timeout_timers_[TIMEOUT_EVENT_COUNT];
+  ReceiverMethod timeout_callbacks_[TIMEOUT_EVENT_COUNT];
+  base::TimeDelta timeout_delays_[TIMEOUT_EVENT_COUNT];
+};
+
+GestureDetector::GestureDetector(
+    const Config& config,
+    GestureListener* listener,
+    DoubleTapListener* optional_double_tap_listener)
+    : timeout_handler_(new TimeoutGestureHandler(config, this)),
+      listener_(listener),
+      double_tap_listener_(optional_double_tap_listener),
+      touch_slop_square_(0),
+      double_tap_touch_slop_square_(0),
+      double_tap_slop_square_(0),
+      two_finger_tap_distance_square_(0),
+      min_fling_velocity_(1),
+      max_fling_velocity_(1),
+      min_swipe_velocity_(0),
+      min_swipe_direction_component_ratio_(0),
+      still_down_(false),
+      defer_confirm_single_tap_(false),
+      always_in_tap_region_(false),
+      always_in_bigger_tap_region_(false),
+      two_finger_tap_allowed_for_gesture_(false),
+      is_double_tapping_(false),
+      last_focus_x_(0),
+      last_focus_y_(0),
+      down_focus_x_(0),
+      down_focus_y_(0),
+      longpress_enabled_(true),
+      showpress_enabled_(true),
+      swipe_enabled_(false),
+      two_finger_tap_enabled_(false) {
+  DCHECK(listener_);
+  Init(config);
+}
+
+GestureDetector::~GestureDetector() {}
+
+bool GestureDetector::OnTouchEvent(const MotionEvent& ev) {
+  const MotionEvent::Action action = ev.GetAction();
+
+  velocity_tracker_.AddMovement(ev);
+
+  const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
+  const int skip_index = pointer_up ? ev.GetActionIndex() : -1;
+
+  // Determine focal point.
+  float sum_x = 0, sum_y = 0;
+  const int count = static_cast<int>(ev.GetPointerCount());
+  for (int i = 0; i < count; i++) {
+    if (skip_index == i)
+      continue;
+    sum_x += ev.GetX(i);
+    sum_y += ev.GetY(i);
+  }
+  const int div = pointer_up ? count - 1 : count;
+  const float focus_x = sum_x / div;
+  const float focus_y = sum_y / div;
+
+  bool handled = false;
+
+  switch (action) {
+    case MotionEvent::ACTION_POINTER_DOWN: {
+      down_focus_x_ = last_focus_x_ = focus_x;
+      down_focus_y_ = last_focus_y_ = focus_y;
+      // Cancel long press and taps.
+      CancelTaps();
+
+      if (!two_finger_tap_allowed_for_gesture_)
+        break;
+
+      const int action_index = ev.GetActionIndex();
+      const float dx = ev.GetX(action_index) - current_down_event_->GetX();
+      const float dy = ev.GetY(action_index) - current_down_event_->GetY();
+
+      if (ev.GetPointerCount() == 2 &&
+          dx * dx + dy * dy < two_finger_tap_distance_square_) {
+        secondary_pointer_down_event_ = ev.Clone();
+      } else {
+        two_finger_tap_allowed_for_gesture_ = false;
+      }
+    } break;
+
+    case MotionEvent::ACTION_POINTER_UP: {
+      down_focus_x_ = last_focus_x_ = focus_x;
+      down_focus_y_ = last_focus_y_ = focus_y;
+
+      // Check the dot product of current velocities.
+      // If the pointer that left was opposing another velocity vector, clear.
+      velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
+      const int up_index = ev.GetActionIndex();
+      const int id1 = ev.GetPointerId(up_index);
+      const float vx1 = velocity_tracker_.GetXVelocity(id1);
+      const float vy1 = velocity_tracker_.GetYVelocity(id1);
+      float vx_total = vx1;
+      float vy_total = vy1;
+      for (int i = 0; i < count; i++) {
+        if (i == up_index)
+          continue;
+
+        const int id2 = ev.GetPointerId(i);
+        const float vx2 = velocity_tracker_.GetXVelocity(id2);
+        const float vy2 = velocity_tracker_.GetYVelocity(id2);
+        const float dot = vx1 * vx2 + vy1 * vy2;
+        if (dot < 0) {
+          vx_total = 0;
+          vy_total = 0;
+          velocity_tracker_.Clear();
+          break;
+        }
+        vx_total += vx2;
+        vy_total += vy2;
+      }
+
+      handled = HandleSwipeIfNeeded(ev, vx_total / count, vy_total / count);
+
+      if (two_finger_tap_allowed_for_gesture_ && ev.GetPointerCount() == 2 &&
+          (ev.GetEventTime() - secondary_pointer_down_event_->GetEventTime() <=
+           two_finger_tap_timeout_)) {
+        handled = listener_->OnTwoFingerTap(*current_down_event_, ev);
+      }
+      two_finger_tap_allowed_for_gesture_ = false;
+    } break;
+
+    case MotionEvent::ACTION_DOWN:
+      if (double_tap_listener_) {
+        bool had_tap_message = timeout_handler_->HasTimeout(TAP);
+        if (had_tap_message)
+          timeout_handler_->StopTimeout(TAP);
+        if (current_down_event_ && previous_up_event_ && had_tap_message &&
+            IsConsideredDoubleTap(
+                *current_down_event_, *previous_up_event_, ev)) {
+          // This is a second tap.
+          is_double_tapping_ = true;
+          // Give a callback with the first tap of the double-tap.
+          handled |= double_tap_listener_->OnDoubleTap(*current_down_event_);
+          // Give a callback with down event of the double-tap.
+          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
+        } else {
+          // This is a first tap.
+          DCHECK(double_tap_timeout_ > base::TimeDelta());
+          timeout_handler_->StartTimeout(TAP);
+        }
+      }
+
+      down_focus_x_ = last_focus_x_ = focus_x;
+      down_focus_y_ = last_focus_y_ = focus_y;
+      current_down_event_ = ev.Clone();
+
+      secondary_pointer_down_event_.reset();
+      always_in_tap_region_ = true;
+      always_in_bigger_tap_region_ = true;
+      still_down_ = true;
+      defer_confirm_single_tap_ = false;
+      two_finger_tap_allowed_for_gesture_ = two_finger_tap_enabled_;
+
+      // Always start the SHOW_PRESS timer before the LONG_PRESS timer to ensure
+      // proper timeout ordering.
+      if (showpress_enabled_)
+        timeout_handler_->StartTimeout(SHOW_PRESS);
+      if (longpress_enabled_)
+        timeout_handler_->StartTimeout(LONG_PRESS);
+      handled |= listener_->OnDown(ev);
+      break;
+
+    case MotionEvent::ACTION_MOVE:
+      {
+        const float scroll_x = last_focus_x_ - focus_x;
+        const float scroll_y = last_focus_y_ - focus_y;
+        if (is_double_tapping_) {
+          // Give the move events of the double-tap.
+          DCHECK(double_tap_listener_);
+          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
+        } else if (always_in_tap_region_) {
+          const float delta_x = focus_x - down_focus_x_;
+          const float delta_y = focus_y - down_focus_y_;
+          const float distance_square = delta_x * delta_x + delta_y * delta_y;
+          if (distance_square > touch_slop_square_) {
+            handled = listener_->OnScroll(
+                *current_down_event_, ev, scroll_x, scroll_y);
+            last_focus_x_ = focus_x;
+            last_focus_y_ = focus_y;
+            always_in_tap_region_ = false;
+            timeout_handler_->Stop();
+          }
+          if (distance_square > double_tap_touch_slop_square_)
+            always_in_bigger_tap_region_ = false;
+        } else if (std::abs(scroll_x) > kScrollEpsilon ||
+                   std::abs(scroll_y) > kScrollEpsilon) {
+          handled =
+              listener_->OnScroll(*current_down_event_, ev, scroll_x, scroll_y);
+          last_focus_x_ = focus_x;
+          last_focus_y_ = focus_y;
+        }
+
+        if (!two_finger_tap_allowed_for_gesture_)
+          break;
+
+        // Two-finger tap should be prevented if either pointer exceeds its
+        // (independent) slop region.
+        const int id0 = current_down_event_->GetPointerId(0);
+        const int ev_idx0 = ev.GetPointerId(0) == id0 ? 0 : 1;
+
+        // Check if the primary pointer exceeded the slop region.
+        float dx = current_down_event_->GetX() - ev.GetX(ev_idx0);
+        float dy = current_down_event_->GetY() - ev.GetY(ev_idx0);
+        if (dx * dx + dy * dy > touch_slop_square_) {
+          two_finger_tap_allowed_for_gesture_ = false;
+          break;
+        }
+        if (ev.GetPointerCount() == 2) {
+          // Check if the secondary pointer exceeded the slop region.
+          const int ev_idx1 = ev_idx0 == 0 ? 1 : 0;
+          const int idx1 = secondary_pointer_down_event_->GetActionIndex();
+          dx = secondary_pointer_down_event_->GetX(idx1) - ev.GetX(ev_idx1);
+          dy = secondary_pointer_down_event_->GetY(idx1) - ev.GetY(ev_idx1);
+          if (dx * dx + dy * dy > touch_slop_square_)
+            two_finger_tap_allowed_for_gesture_ = false;
+        }
+      }
+      break;
+
+    case MotionEvent::ACTION_UP:
+      still_down_ = false;
+      {
+        if (is_double_tapping_) {
+          // Finally, give the up event of the double-tap.
+          DCHECK(double_tap_listener_);
+          handled |= double_tap_listener_->OnDoubleTapEvent(ev);
+        } else if (always_in_tap_region_) {
+          handled = listener_->OnSingleTapUp(ev);
+          if (defer_confirm_single_tap_ && double_tap_listener_ != NULL) {
+            double_tap_listener_->OnSingleTapConfirmed(ev);
+          }
+        } else {
+
+          // A fling must travel the minimum tap distance.
+          const int pointer_id = ev.GetPointerId(0);
+          velocity_tracker_.ComputeCurrentVelocity(1000, max_fling_velocity_);
+          const float velocity_y = velocity_tracker_.GetYVelocity(pointer_id);
+          const float velocity_x = velocity_tracker_.GetXVelocity(pointer_id);
+
+          if ((std::abs(velocity_y) > min_fling_velocity_) ||
+              (std::abs(velocity_x) > min_fling_velocity_)) {
+            handled = listener_->OnFling(
+                *current_down_event_, ev, velocity_x, velocity_y);
+          }
+
+          handled |= HandleSwipeIfNeeded(ev, velocity_x, velocity_y);
+        }
+
+        previous_up_event_ = ev.Clone();
+
+        velocity_tracker_.Clear();
+        is_double_tapping_ = false;
+        defer_confirm_single_tap_ = false;
+        timeout_handler_->StopTimeout(SHOW_PRESS);
+        timeout_handler_->StopTimeout(LONG_PRESS);
+      }
+      break;
+
+    case MotionEvent::ACTION_CANCEL:
+      Cancel();
+      break;
+  }
+
+  return handled;
+}
+
+void GestureDetector::SetDoubleTapListener(
+    DoubleTapListener* double_tap_listener) {
+  if (double_tap_listener == double_tap_listener_)
+    return;
+
+  DCHECK(!is_double_tapping_);
+
+  // Null'ing the double-tap listener should flush an active tap timeout.
+  if (!double_tap_listener) {
+    if (timeout_handler_->HasTimeout(TAP)) {
+      timeout_handler_->StopTimeout(TAP);
+      OnTapTimeout();
+    }
+  }
+
+  double_tap_listener_ = double_tap_listener;
+}
+
+void GestureDetector::Init(const Config& config) {
+  DCHECK(listener_);
+
+  const float touch_slop = config.touch_slop + kSlopEpsilon;
+  const float double_tap_touch_slop = touch_slop;
+  const float double_tap_slop = config.double_tap_slop + kSlopEpsilon;
+  touch_slop_square_ = touch_slop * touch_slop;
+  double_tap_touch_slop_square_ = double_tap_touch_slop * double_tap_touch_slop;
+  double_tap_slop_square_ = double_tap_slop * double_tap_slop;
+  double_tap_timeout_ = config.double_tap_timeout;
+  double_tap_min_time_ = config.double_tap_min_time;
+  DCHECK(double_tap_min_time_ < double_tap_timeout_);
+  min_fling_velocity_ = config.minimum_fling_velocity;
+  max_fling_velocity_ = config.maximum_fling_velocity;
+
+  swipe_enabled_ = config.swipe_enabled;
+  min_swipe_velocity_ = config.minimum_swipe_velocity;
+  DCHECK_GT(config.maximum_swipe_deviation_angle, 0);
+  DCHECK_LE(config.maximum_swipe_deviation_angle, 45);
+  const float maximum_swipe_deviation_angle =
+      std::min(45.f, std::max(0.001f, config.maximum_swipe_deviation_angle));
+  min_swipe_direction_component_ratio_ =
+      1.f / tan(maximum_swipe_deviation_angle * kDegreesToRadians);
+
+  two_finger_tap_enabled_ = config.two_finger_tap_enabled;
+  two_finger_tap_distance_square_ = config.two_finger_tap_max_separation *
+                                    config.two_finger_tap_max_separation;
+  two_finger_tap_timeout_ = config.two_finger_tap_timeout;
+}
+
+void GestureDetector::OnShowPressTimeout() {
+  listener_->OnShowPress(*current_down_event_);
+}
+
+void GestureDetector::OnLongPressTimeout() {
+  timeout_handler_->StopTimeout(TAP);
+  defer_confirm_single_tap_ = false;
+  listener_->OnLongPress(*current_down_event_);
+}
+
+void GestureDetector::OnTapTimeout() {
+  if (!double_tap_listener_)
+    return;
+  if (!still_down_)
+    double_tap_listener_->OnSingleTapConfirmed(*current_down_event_);
+  else
+    defer_confirm_single_tap_ = true;
+}
+
+void GestureDetector::Cancel() {
+  CancelTaps();
+  velocity_tracker_.Clear();
+  still_down_ = false;
+}
+
+void GestureDetector::CancelTaps() {
+  timeout_handler_->Stop();
+  is_double_tapping_ = false;
+  always_in_tap_region_ = false;
+  always_in_bigger_tap_region_ = false;
+  defer_confirm_single_tap_ = false;
+}
+
+bool GestureDetector::IsConsideredDoubleTap(
+    const MotionEvent& first_down,
+    const MotionEvent& first_up,
+    const MotionEvent& second_down) const {
+  if (!always_in_bigger_tap_region_)
+    return false;
+
+  const base::TimeDelta delta_time =
+      second_down.GetEventTime() - first_up.GetEventTime();
+  if (delta_time < double_tap_min_time_ || delta_time > double_tap_timeout_)
+    return false;
+
+  const float delta_x = first_down.GetX() - second_down.GetX();
+  const float delta_y = first_down.GetY() - second_down.GetY();
+  return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square_);
+}
+
+bool GestureDetector::HandleSwipeIfNeeded(const MotionEvent& up,
+                                          float vx,
+                                          float vy) {
+  if (!swipe_enabled_ || (!vx && !vy))
+    return false;
+  float vx_abs = std::abs(vx);
+  float vy_abs = std::abs(vy);
+
+  if (vx_abs < min_swipe_velocity_)
+    vx_abs = vx = 0;
+  if (vy_abs < min_swipe_velocity_)
+    vy_abs = vy = 0;
+
+  // Note that the ratio will be 0 if both velocites are below the min.
+  float ratio = vx_abs > vy_abs ? vx_abs / std::max(vy_abs, 0.001f)
+                                : vy_abs / std::max(vx_abs, 0.001f);
+
+  if (ratio < min_swipe_direction_component_ratio_)
+    return false;
+
+  if (vx_abs > vy_abs)
+    vy = 0;
+  else
+    vx = 0;
+  return listener_->OnSwipe(*current_down_event_, up, vx, vy);
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_detector.h b/ui/events/gesture_detection/gesture_detector.h
new file mode 100644
index 0000000..91b2786
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_detector.h
@@ -0,0 +1,153 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/velocity_tracker_state.h"
+
+namespace ui {
+
+class DoubleTapListener;
+class GestureListener;
+class MotionEvent;
+
+// Port of GestureDetector.java from Android
+// * platform/frameworks/base/core/java/android/view/GestureDetector.java
+// * Change-Id: Ib470735ec929b0b358fca4597e92dc81084e675f
+// * Please update the Change-Id as upstream Android changes are pulled.
+class GESTURE_DETECTION_EXPORT GestureDetector {
+ public:
+  struct GESTURE_DETECTION_EXPORT Config {
+    Config();
+    ~Config();
+
+    base::TimeDelta longpress_timeout;
+    base::TimeDelta showpress_timeout;
+    base::TimeDelta double_tap_timeout;
+
+    // The minimum duration between the first tap's up event and the second
+    // tap's down event for an interaction to be considered a double-tap.
+    base::TimeDelta double_tap_min_time;
+
+    // Distance a touch can wander before a scroll will occur (in dips).
+    float touch_slop;
+
+    // Distance the first touch can wander before it is no longer considered a
+    // double tap (in dips).
+    float double_tap_slop;
+
+    // Minimum velocity to initiate a fling (in dips/second).
+    float minimum_fling_velocity;
+
+    // Maximum velocity of an initiated fling (in dips/second).
+    float maximum_fling_velocity;
+
+    // Whether |OnSwipe| should be called after a secondary touch is released
+    // while a logical swipe gesture is active. Defaults to false.
+    bool swipe_enabled;
+
+    // Minimum velocity to initiate a swipe (in dips/second).
+    float minimum_swipe_velocity;
+
+    // Maximum angle of the swipe from its dominant component axis, between
+    // (0, 45] degrees. The closer this is to 0, the closer the dominant
+    // direction of the swipe must be to up, down left or right.
+    float maximum_swipe_deviation_angle;
+
+    // Whether |OnTwoFingerTap| should be called for two finger tap
+    // gestures. Defaults to false.
+    bool two_finger_tap_enabled;
+
+    // Maximum distance between pointers for a two finger tap.
+    float two_finger_tap_max_separation;
+
+    // Maximum time the second pointer can be active for a two finger tap.
+    base::TimeDelta two_finger_tap_timeout;
+  };
+
+  GestureDetector(const Config& config,
+                  GestureListener* listener,
+                  DoubleTapListener* optional_double_tap_listener);
+  ~GestureDetector();
+
+  bool OnTouchEvent(const MotionEvent& ev);
+
+  // Setting a valid |double_tap_listener| will enable double-tap detection,
+  // wherein calls to |OnSimpleTapConfirmed| are delayed by the tap timeout.
+  // Note: The listener must never be changed while |is_double_tapping| is true.
+  void SetDoubleTapListener(DoubleTapListener* double_tap_listener);
+
+  bool has_doubletap_listener() const { return double_tap_listener_ != NULL; }
+
+  bool is_double_tapping() const { return is_double_tapping_; }
+
+  void set_longpress_enabled(bool enabled) { longpress_enabled_ = enabled; }
+  void set_showpress_enabled(bool enabled) { showpress_enabled_ = enabled; }
+
+ private:
+  void Init(const Config& config);
+  void OnShowPressTimeout();
+  void OnLongPressTimeout();
+  void OnTapTimeout();
+  void Cancel();
+  void CancelTaps();
+  bool IsConsideredDoubleTap(const MotionEvent& first_down,
+                             const MotionEvent& first_up,
+                             const MotionEvent& second_down) const;
+  bool HandleSwipeIfNeeded(const MotionEvent& up, float vx, float vy);
+
+  class TimeoutGestureHandler;
+  scoped_ptr<TimeoutGestureHandler> timeout_handler_;
+  GestureListener* const listener_;
+  DoubleTapListener* double_tap_listener_;
+
+  float touch_slop_square_;
+  float double_tap_touch_slop_square_;
+  float double_tap_slop_square_;
+  float two_finger_tap_distance_square_;
+  float min_fling_velocity_;
+  float max_fling_velocity_;
+  float min_swipe_velocity_;
+  float min_swipe_direction_component_ratio_;
+  base::TimeDelta double_tap_timeout_;
+  base::TimeDelta two_finger_tap_timeout_;
+  base::TimeDelta double_tap_min_time_;
+
+  bool still_down_;
+  bool defer_confirm_single_tap_;
+  bool always_in_tap_region_;
+  bool always_in_bigger_tap_region_;
+  bool two_finger_tap_allowed_for_gesture_;
+
+  scoped_ptr<MotionEvent> current_down_event_;
+  scoped_ptr<MotionEvent> previous_up_event_;
+  scoped_ptr<MotionEvent> secondary_pointer_down_event_;
+
+  // True when the user is still touching for the second tap (down, move, and
+  // up events). Can only be true if there is a double tap listener attached.
+  bool is_double_tapping_;
+
+  float last_focus_x_;
+  float last_focus_y_;
+  float down_focus_x_;
+  float down_focus_y_;
+
+  bool longpress_enabled_;
+  bool showpress_enabled_;
+  bool swipe_enabled_;
+  bool two_finger_tap_enabled_;
+
+  // Determines speed during touch scrolling.
+  VelocityTrackerState velocity_tracker_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureDetector);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_DETECTOR_H_
diff --git a/ui/events/gesture_detection/gesture_event_data.cc b/ui/events/gesture_detection/gesture_event_data.cc
new file mode 100644
index 0000000..2102f23
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_event_data.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+GestureEventData::GestureEventData(const GestureEventDetails& details,
+                                   int motion_event_id,
+                                   MotionEvent::ToolType primary_tool_type,
+                                   base::TimeTicks time,
+                                   float x,
+                                   float y,
+                                   float raw_x,
+                                   float raw_y,
+                                   size_t touch_point_count,
+                                   const gfx::RectF& bounding_box,
+                                   int flags)
+    : details(details),
+      motion_event_id(motion_event_id),
+      primary_tool_type(primary_tool_type),
+      time(time),
+      x(x),
+      y(y),
+      raw_x(raw_x),
+      raw_y(raw_y),
+      flags(flags) {
+  DCHECK_GE(motion_event_id, 0);
+  DCHECK_NE(0U, touch_point_count);
+  this->details.set_touch_points(static_cast<int>(touch_point_count));
+  this->details.set_bounding_box(bounding_box);
+}
+
+GestureEventData::GestureEventData(EventType type,
+                                   const GestureEventData& other)
+    : details(type),
+      motion_event_id(other.motion_event_id),
+      primary_tool_type(other.primary_tool_type),
+      time(other.time),
+      x(other.x),
+      y(other.y),
+      raw_x(other.raw_x),
+      raw_y(other.raw_y),
+      flags(other.flags) {
+  details.set_touch_points(other.details.touch_points());
+  details.set_bounding_box(other.details.bounding_box_f());
+}
+
+GestureEventData::GestureEventData()
+    : motion_event_id(0),
+      primary_tool_type(MotionEvent::TOOL_TYPE_UNKNOWN),
+      x(0),
+      y(0),
+      raw_x(0),
+      raw_y(0),
+      flags(EF_NONE) {
+}
+
+}  //  namespace ui
diff --git a/ui/events/gesture_detection/gesture_event_data.h b/ui/events/gesture_detection/gesture_event_data.h
new file mode 100644
index 0000000..8727c6d
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data.h
@@ -0,0 +1,53 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_H_
+
+#include "base/time/time.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/gesture_event_details.h"
+
+namespace ui {
+
+class GestureEventDataPacket;
+
+struct GESTURE_DETECTION_EXPORT GestureEventData {
+  GestureEventData(const GestureEventDetails&,
+                   int motion_event_id,
+                   MotionEvent::ToolType primary_tool_type,
+                   base::TimeTicks time,
+                   float x,
+                   float y,
+                   float raw_x,
+                   float raw_y,
+                   size_t touch_point_count,
+                   const gfx::RectF& bounding_box,
+                   int flags);
+  GestureEventData(EventType type, const GestureEventData&);
+
+  EventType type() const { return details.type(); }
+
+  GestureEventDetails details;
+  int motion_event_id;
+  MotionEvent::ToolType primary_tool_type;
+  base::TimeTicks time;
+  float x;
+  float y;
+  float raw_x;
+  float raw_y;
+  int flags;
+
+ private:
+  friend class GestureEventDataPacket;
+
+  // Initializes type to GESTURE_TYPE_INVALID.
+  GestureEventData();
+};
+
+}  //  namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_H_
diff --git a/ui/events/gesture_detection/gesture_event_data_packet.cc b/ui/events/gesture_detection/gesture_event_data_packet.cc
new file mode 100644
index 0000000..265300c
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data_packet.cc
@@ -0,0 +1,96 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_event_data_packet.h"
+
+#include "base/logging.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+namespace {
+
+GestureEventDataPacket::GestureSource ToGestureSource(
+    const ui::MotionEvent& event) {
+  switch (event.GetAction()) {
+    case ui::MotionEvent::ACTION_DOWN:
+      return GestureEventDataPacket::TOUCH_SEQUENCE_START;
+    case ui::MotionEvent::ACTION_UP:
+      return GestureEventDataPacket::TOUCH_SEQUENCE_END;
+    case ui::MotionEvent::ACTION_MOVE:
+      return GestureEventDataPacket::TOUCH_MOVE;
+    case ui::MotionEvent::ACTION_CANCEL:
+      return GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL;
+    case ui::MotionEvent::ACTION_POINTER_DOWN:
+      return GestureEventDataPacket::TOUCH_START;
+    case ui::MotionEvent::ACTION_POINTER_UP:
+      return GestureEventDataPacket::TOUCH_END;
+  };
+  NOTREACHED() << "Invalid ui::MotionEvent action: " << event.GetAction();
+  return GestureEventDataPacket::INVALID;
+}
+
+}  // namespace
+
+GestureEventDataPacket::GestureEventDataPacket()
+    : gesture_source_(UNDEFINED) {
+}
+
+GestureEventDataPacket::GestureEventDataPacket(
+    base::TimeTicks timestamp,
+    GestureSource source,
+    const gfx::PointF& touch_location,
+    const gfx::PointF& raw_touch_location)
+    : timestamp_(timestamp),
+      touch_location_(touch_location),
+      raw_touch_location_(raw_touch_location),
+      gesture_source_(source) {
+  DCHECK_NE(gesture_source_, UNDEFINED);
+}
+
+GestureEventDataPacket::GestureEventDataPacket(
+    const GestureEventDataPacket& other)
+    : timestamp_(other.timestamp_),
+      gestures_(other.gestures_),
+      touch_location_(other.touch_location_),
+      raw_touch_location_(other.raw_touch_location_),
+      gesture_source_(other.gesture_source_) {
+}
+
+GestureEventDataPacket::~GestureEventDataPacket() {
+}
+
+GestureEventDataPacket& GestureEventDataPacket::operator=(
+    const GestureEventDataPacket& other) {
+  timestamp_ = other.timestamp_;
+  gesture_source_ = other.gesture_source_;
+  touch_location_ = other.touch_location_;
+  raw_touch_location_ = other.raw_touch_location_;
+  gestures_ = other.gestures_;
+  return *this;
+}
+
+void GestureEventDataPacket::Push(const GestureEventData& gesture) {
+  DCHECK_NE(ET_UNKNOWN, gesture.type());
+  gestures_->push_back(gesture);
+}
+
+GestureEventDataPacket GestureEventDataPacket::FromTouch(
+    const ui::MotionEvent& touch) {
+  return GestureEventDataPacket(touch.GetEventTime(),
+                                ToGestureSource(touch),
+                                gfx::PointF(touch.GetX(), touch.GetY()),
+                                gfx::PointF(touch.GetRawX(), touch.GetRawY()));
+}
+
+GestureEventDataPacket GestureEventDataPacket::FromTouchTimeout(
+    const GestureEventData& gesture) {
+  GestureEventDataPacket packet(gesture.time,
+                                TOUCH_TIMEOUT,
+                                gfx::PointF(gesture.x, gesture.y),
+                                gfx::PointF(gesture.raw_x, gesture.raw_y));
+  packet.Push(gesture);
+  return packet;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_event_data_packet.h b/ui/events/gesture_detection/gesture_event_data_packet.h
new file mode 100644
index 0000000..9b115de
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data_packet.h
@@ -0,0 +1,67 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_
+
+#include "base/containers/stack_container.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// Acts as a transport container for gestures created (directly or indirectly)
+// by a touch event.
+class GESTURE_DETECTION_EXPORT GestureEventDataPacket {
+ public:
+  enum GestureSource {
+    UNDEFINED = -1,        // Used only for a default-constructed packet.
+    INVALID,               // The source of the gesture was invalid.
+    TOUCH_SEQUENCE_START,  // The start of a new gesture sequence.
+    TOUCH_SEQUENCE_END,    // The end of a gesture sequence.
+    TOUCH_SEQUENCE_CANCEL, // The gesture sequence was cancelled.
+    TOUCH_START,           // A touch down occured during a gesture sequence.
+    TOUCH_MOVE,            // A touch move occured during a gesture sequence.
+    TOUCH_END,             // A touch up occured during a gesture sequence.
+    TOUCH_TIMEOUT,         // Timeout from an existing gesture sequence.
+  };
+
+  GestureEventDataPacket();
+  GestureEventDataPacket(const GestureEventDataPacket& other);
+  ~GestureEventDataPacket();
+  GestureEventDataPacket& operator=(const GestureEventDataPacket& other);
+
+  // Factory methods for creating a packet from a particular event.
+  static GestureEventDataPacket FromTouch(const ui::MotionEvent& touch);
+  static GestureEventDataPacket FromTouchTimeout(
+      const GestureEventData& gesture);
+
+  void Push(const GestureEventData& gesture);
+
+  const base::TimeTicks& timestamp() const { return timestamp_; }
+  const GestureEventData& gesture(size_t i) const { return gestures_[i]; }
+  size_t gesture_count() const { return gestures_->size(); }
+  GestureSource gesture_source() const { return gesture_source_; }
+  const gfx::PointF& touch_location() const { return touch_location_; }
+  const gfx::PointF& raw_touch_location() const { return raw_touch_location_; }
+
+ private:
+  GestureEventDataPacket(base::TimeTicks timestamp,
+                         GestureSource source,
+                         const gfx::PointF& touch_location,
+                         const gfx::PointF& raw_touch_location);
+
+  enum { kTypicalMaxGesturesPerTouch = 5 };
+  base::TimeTicks timestamp_;
+  base::StackVector<GestureEventData, kTypicalMaxGesturesPerTouch> gestures_;
+  gfx::PointF touch_location_;
+  gfx::PointF raw_touch_location_;
+  GestureSource gesture_source_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_
diff --git a/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc b/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
new file mode 100644
index 0000000..f8af11e
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_event_data_packet_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/gesture_event_data_packet.h"
+#include "ui/events/test/mock_motion_event.h"
+
+using ui::test::MockMotionEvent;
+
+namespace ui {
+namespace {
+
+const float kTouchX = 13.7f;
+const float kTouchY = 14.2f;
+
+GestureEventData CreateGesture(EventType type) {
+  return GestureEventData(GestureEventDetails(type),
+                          0,
+                          MotionEvent::TOOL_TYPE_FINGER,
+                          base::TimeTicks(),
+                          kTouchX,
+                          kTouchY,
+                          kTouchX + 5.f,
+                          kTouchY + 10.f,
+                          1,
+                          gfx::RectF(kTouchX - 1.f, kTouchY - 1.f, 2.f, 2.f),
+                          EF_NONE);
+}
+
+}  // namespace
+
+bool GestureEquals(const GestureEventData& lhs, const GestureEventData& rhs) {
+  return lhs.type() == rhs.type() &&
+         lhs.motion_event_id == rhs.motion_event_id &&
+         lhs.primary_tool_type == rhs.primary_tool_type &&
+         lhs.time == rhs.time && lhs.x == rhs.x && lhs.y == rhs.y &&
+         lhs.raw_x == rhs.raw_x && lhs.raw_y == rhs.raw_y;
+}
+
+bool PacketEquals(const GestureEventDataPacket& lhs,
+                  const GestureEventDataPacket& rhs) {
+  if (lhs.timestamp() != rhs.timestamp() ||
+      lhs.gesture_count() != rhs.gesture_count() ||
+      lhs.timestamp() != rhs.timestamp() ||
+      lhs.gesture_source() != rhs.gesture_source() ||
+      lhs.touch_location() != rhs.touch_location() ||
+      lhs.raw_touch_location() != rhs.raw_touch_location())
+    return false;
+
+  for (size_t i = 0; i < lhs.gesture_count(); ++i) {
+    if (!GestureEquals(lhs.gesture(i), rhs.gesture(i)))
+      return false;
+  }
+
+  return true;
+}
+
+class GestureEventDataPacketTest : public testing::Test {};
+
+TEST_F(GestureEventDataPacketTest, Basic) {
+  base::TimeTicks touch_time = base::TimeTicks::Now();
+
+  GestureEventDataPacket packet;
+  EXPECT_EQ(0U, packet.gesture_count());
+  EXPECT_EQ(GestureEventDataPacket::UNDEFINED, packet.gesture_source());
+
+  packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_DOWN, touch_time, kTouchX, kTouchY));
+  EXPECT_TRUE(touch_time == packet.timestamp());
+  EXPECT_EQ(0U, packet.gesture_count());
+  EXPECT_EQ(gfx::PointF(kTouchX, kTouchY), packet.touch_location());
+
+  for (size_t i = ET_GESTURE_TYPE_START; i < ET_GESTURE_TYPE_END; ++i) {
+    const EventType type = static_cast<EventType>(i);
+    GestureEventData gesture = CreateGesture(type);
+    packet.Push(gesture);
+    const size_t index = (i - ET_GESTURE_TYPE_START);
+    ASSERT_EQ(index + 1U, packet.gesture_count());
+    EXPECT_TRUE(GestureEquals(gesture, packet.gesture(index)));
+  }
+}
+
+TEST_F(GestureEventDataPacketTest, Copy) {
+  GestureEventDataPacket packet0 = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_UP));
+  packet0.Push(CreateGesture(ET_GESTURE_TAP_DOWN));
+  packet0.Push(CreateGesture(ET_GESTURE_SCROLL_BEGIN));
+
+  GestureEventDataPacket packet1 = packet0;
+  EXPECT_TRUE(PacketEquals(packet0, packet1));
+
+  packet0 = packet1;
+  EXPECT_TRUE(PacketEquals(packet1, packet0));
+}
+
+TEST_F(GestureEventDataPacketTest, GestureSource) {
+  GestureEventDataPacket packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_DOWN));
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_SEQUENCE_START,
+            packet.gesture_source());
+
+  packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_UP));
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_SEQUENCE_END,
+            packet.gesture_source());
+
+  packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_CANCEL));
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL,
+            packet.gesture_source());
+
+  packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_MOVE));
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_MOVE, packet.gesture_source());
+
+  packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_POINTER_DOWN));
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_START, packet.gesture_source());
+
+  packet = GestureEventDataPacket::FromTouch(
+      MockMotionEvent(MotionEvent::ACTION_POINTER_UP));
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_END, packet.gesture_source());
+
+  GestureEventData gesture = CreateGesture(ET_GESTURE_TAP);
+  packet = GestureEventDataPacket::FromTouchTimeout(gesture);
+  EXPECT_EQ(GestureEventDataPacket::TOUCH_TIMEOUT, packet.gesture_source());
+  EXPECT_EQ(1U, packet.gesture_count());
+  EXPECT_EQ(base::TimeTicks(), packet.timestamp());
+  EXPECT_EQ(gfx::PointF(gesture.x, gesture.y), packet.touch_location());
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_listeners.cc b/ui/events/gesture_detection/gesture_listeners.cc
new file mode 100644
index 0000000..8917f26
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_listeners.cc
@@ -0,0 +1,61 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_listeners.h"
+
+namespace ui {
+
+bool SimpleGestureListener::OnDown(const MotionEvent& e) {
+  return false;
+}
+
+void SimpleGestureListener::OnShowPress(const MotionEvent& e) {
+}
+
+bool SimpleGestureListener::OnSingleTapUp(const MotionEvent& e) {
+  return false;
+}
+
+void SimpleGestureListener::OnLongPress(const MotionEvent& e) {
+}
+
+bool SimpleGestureListener::OnScroll(const MotionEvent& e1,
+                                     const MotionEvent& e2,
+                                     float distance_x,
+                                     float distance_y) {
+  return false;
+}
+
+bool SimpleGestureListener::OnFling(const MotionEvent& e1,
+                                    const MotionEvent& e2,
+                                    float velocity_x,
+                                    float velocity_y) {
+  return false;
+}
+
+bool SimpleGestureListener::OnSwipe(const MotionEvent& e1,
+                                    const MotionEvent& e2,
+                                    float velocity_x,
+                                    float velocity_y) {
+  return false;
+}
+
+bool SimpleGestureListener::OnTwoFingerTap(const MotionEvent& e1,
+                                           const MotionEvent& e2) {
+  return false;
+}
+
+bool SimpleGestureListener::OnSingleTapConfirmed(const MotionEvent& e) {
+  return false;
+}
+
+bool SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
+  return false;
+}
+
+bool SimpleGestureListener::OnDoubleTapEvent(const MotionEvent& e) {
+  return false;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_listeners.h b/ui/events/gesture_detection/gesture_listeners.h
new file mode 100644
index 0000000..1d62347
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_listeners.h
@@ -0,0 +1,83 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_LISTENERS_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_LISTENERS_H_
+
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// Client through which |GestureDetector| signals gesture detection.
+class GESTURE_DETECTION_EXPORT GestureListener {
+ public:
+  virtual ~GestureListener() {}
+  virtual bool OnDown(const MotionEvent& e) = 0;
+  virtual void OnShowPress(const MotionEvent& e) = 0;
+  virtual bool OnSingleTapUp(const MotionEvent& e) = 0;
+  virtual void OnLongPress(const MotionEvent& e) = 0;
+  virtual bool OnScroll(const MotionEvent& e1,
+                        const MotionEvent& e2,
+                        float distance_x,
+                        float distance_y) = 0;
+  virtual bool OnFling(const MotionEvent& e1,
+                       const MotionEvent& e2,
+                       float velocity_x,
+                       float velocity_y) = 0;
+  // Added for Chromium (Aura).
+  virtual bool OnSwipe(const MotionEvent& e1,
+                       const MotionEvent& e2,
+                       float velocity_x,
+                       float velocity_y) = 0;
+  virtual bool OnTwoFingerTap(const MotionEvent& e1, const MotionEvent& e2) = 0;
+};
+
+// Client through which |GestureDetector| signals double-tap detection.
+class GESTURE_DETECTION_EXPORT DoubleTapListener {
+ public:
+  virtual ~DoubleTapListener() {}
+  virtual bool OnSingleTapConfirmed(const MotionEvent& e) = 0;
+  virtual bool OnDoubleTap(const MotionEvent& e) = 0;
+  virtual bool OnDoubleTapEvent(const MotionEvent& e) = 0;
+};
+
+// A convenience class to extend when you only want to listen for a subset
+// of all the gestures. This implements all methods in the
+// |GestureListener| and |DoubleTapListener| but does
+// nothing and returns false for all applicable methods.
+class GESTURE_DETECTION_EXPORT SimpleGestureListener
+    : public GestureListener,
+      public DoubleTapListener {
+ public:
+  // GestureListener implementation.
+  virtual bool OnDown(const MotionEvent& e) override;
+  virtual void OnShowPress(const MotionEvent& e) override;
+  virtual bool OnSingleTapUp(const MotionEvent& e) override;
+  virtual void OnLongPress(const MotionEvent& e) override;
+  virtual bool OnScroll(const MotionEvent& e1,
+                        const MotionEvent& e2,
+                        float distance_x,
+                        float distance_y) override;
+  virtual bool OnFling(const MotionEvent& e1,
+                       const MotionEvent& e2,
+                       float velocity_x,
+                       float velocity_y) override;
+  virtual bool OnSwipe(const MotionEvent& e1,
+                       const MotionEvent& e2,
+                       float velocity_x,
+                       float velocity_y) override;
+  virtual bool OnTwoFingerTap(const MotionEvent& e1,
+                              const MotionEvent& e2) override;
+
+  // DoubleTapListener implementation.
+  virtual bool OnSingleTapConfirmed(const MotionEvent& e) override;
+  virtual bool OnDoubleTap(const MotionEvent& e) override;
+  virtual bool OnDoubleTapEvent(const MotionEvent& e) override;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_LISTENERS_H_
diff --git a/ui/events/gesture_detection/gesture_provider.cc b/ui/events/gesture_detection/gesture_provider.cc
new file mode 100644
index 0000000..51d119b
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider.cc
@@ -0,0 +1,822 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_provider.h"
+
+#include <cmath>
+
+#include "base/auto_reset.h"
+#include "base/debug/trace_event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+#include "ui/events/gesture_detection/gesture_listeners.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/gesture_detection/scale_gesture_listeners.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace ui {
+namespace {
+
+// Double-tap drag zoom sensitivity (speed).
+const float kDoubleTapDragZoomSpeed = 0.005f;
+
+const char* GetMotionEventActionName(MotionEvent::Action action) {
+  switch (action) {
+    case MotionEvent::ACTION_POINTER_DOWN:
+      return "ACTION_POINTER_DOWN";
+    case MotionEvent::ACTION_POINTER_UP:
+      return "ACTION_POINTER_UP";
+    case MotionEvent::ACTION_DOWN:
+      return "ACTION_DOWN";
+    case MotionEvent::ACTION_UP:
+      return "ACTION_UP";
+    case MotionEvent::ACTION_CANCEL:
+      return "ACTION_CANCEL";
+    case MotionEvent::ACTION_MOVE:
+      return "ACTION_MOVE";
+  }
+  return "";
+}
+
+gfx::RectF ClampBoundingBox(const gfx::RectF& bounds,
+                            float min_length,
+                            float max_length) {
+  float width = bounds.width();
+  float height = bounds.height();
+  if (min_length) {
+    width = std::max(min_length, width);
+    height = std::max(min_length, height);
+  }
+  if (max_length) {
+    width = std::min(max_length, width);
+    height = std::min(max_length, height);
+  }
+  const gfx::PointF center = bounds.CenterPoint();
+  return gfx::RectF(
+      center.x() - width / 2.f, center.y() - height / 2.f, width, height);
+}
+
+}  // namespace
+
+// GestureProvider:::Config
+
+GestureProvider::Config::Config()
+    : display(gfx::Display::kInvalidDisplayID, gfx::Rect(1, 1)),
+      disable_click_delay(false),
+      gesture_begin_end_types_enabled(false),
+      min_gesture_bounds_length(0),
+      max_gesture_bounds_length(0) {
+}
+
+GestureProvider::Config::~Config() {
+}
+
+// GestureProvider::GestureListener
+
+class GestureProvider::GestureListenerImpl : public ScaleGestureListener,
+                                             public GestureListener,
+                                             public DoubleTapListener {
+ public:
+  GestureListenerImpl(const GestureProvider::Config& config,
+                      GestureProviderClient* client)
+      : config_(config),
+        client_(client),
+        gesture_detector_(config.gesture_detector_config, this, this),
+        scale_gesture_detector_(config.scale_gesture_detector_config, this),
+        snap_scroll_controller_(config.display),
+        ignore_multitouch_zoom_events_(false),
+        ignore_single_tap_(false),
+        pinch_event_sent_(false),
+        scroll_event_sent_(false),
+        max_diameter_before_show_press_(0),
+        show_press_event_sent_(false) {}
+
+  void OnTouchEvent(const MotionEvent& event) {
+    const bool in_scale_gesture = IsScaleGestureDetectionInProgress();
+    snap_scroll_controller_.SetSnapScrollingMode(event, in_scale_gesture);
+    if (in_scale_gesture)
+      SetIgnoreSingleTap(true);
+
+    const MotionEvent::Action action = event.GetAction();
+    if (action == MotionEvent::ACTION_DOWN) {
+      current_down_time_ = event.GetEventTime();
+      current_longpress_time_ = base::TimeTicks();
+      ignore_single_tap_ = false;
+      scroll_event_sent_ = false;
+      pinch_event_sent_ = false;
+      show_press_event_sent_ = false;
+      gesture_detector_.set_longpress_enabled(true);
+      tap_down_point_ = gfx::PointF(event.GetX(), event.GetY());
+      max_diameter_before_show_press_ = event.GetTouchMajor();
+    }
+
+    gesture_detector_.OnTouchEvent(event);
+    scale_gesture_detector_.OnTouchEvent(event);
+
+    if (action == MotionEvent::ACTION_UP ||
+        action == MotionEvent::ACTION_CANCEL) {
+      // Note: This call will have no effect if a fling was just generated, as
+      // |Fling()| will have already signalled an end to touch-scrolling.
+      if (scroll_event_sent_)
+        Send(CreateGesture(ET_GESTURE_SCROLL_END, event));
+      current_down_time_ = base::TimeTicks();
+    } else if (action == MotionEvent::ACTION_MOVE) {
+      if (!show_press_event_sent_ && !scroll_event_sent_) {
+        max_diameter_before_show_press_ =
+            std::max(max_diameter_before_show_press_, event.GetTouchMajor());
+      }
+    }
+  }
+
+  void Send(GestureEventData gesture) {
+    DCHECK(!gesture.time.is_null());
+    // The only valid events that should be sent without an active touch
+    // sequence are SHOW_PRESS and TAP, potentially triggered by the double-tap
+    // delay timing out.
+    DCHECK(!current_down_time_.is_null() || gesture.type() == ET_GESTURE_TAP ||
+           gesture.type() == ET_GESTURE_SHOW_PRESS ||
+           gesture.type() == ET_GESTURE_BEGIN ||
+           gesture.type() == ET_GESTURE_END);
+
+    if (gesture.primary_tool_type == MotionEvent::TOOL_TYPE_UNKNOWN ||
+        gesture.primary_tool_type == MotionEvent::TOOL_TYPE_FINGER) {
+      gesture.details.set_bounding_box(
+          ClampBoundingBox(gesture.details.bounding_box_f(),
+                           config_.min_gesture_bounds_length,
+                           config_.max_gesture_bounds_length));
+    }
+
+    switch (gesture.type()) {
+      case ET_GESTURE_LONG_PRESS:
+        DCHECK(!IsScaleGestureDetectionInProgress());
+        current_longpress_time_ = gesture.time;
+        break;
+      case ET_GESTURE_LONG_TAP:
+        current_longpress_time_ = base::TimeTicks();
+        break;
+      case ET_GESTURE_SCROLL_BEGIN:
+        DCHECK(!scroll_event_sent_);
+        scroll_event_sent_ = true;
+        break;
+      case ET_GESTURE_SCROLL_END:
+        DCHECK(scroll_event_sent_);
+        if (pinch_event_sent_)
+          Send(GestureEventData(ET_GESTURE_PINCH_END, gesture));
+        scroll_event_sent_ = false;
+        break;
+      case ET_SCROLL_FLING_START:
+        DCHECK(scroll_event_sent_);
+        scroll_event_sent_ = false;
+        break;
+      case ET_GESTURE_PINCH_BEGIN:
+        DCHECK(!pinch_event_sent_);
+        if (!scroll_event_sent_)
+          Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN, gesture));
+        pinch_event_sent_ = true;
+        break;
+      case ET_GESTURE_PINCH_END:
+        DCHECK(pinch_event_sent_);
+        pinch_event_sent_ = false;
+        break;
+      case ET_GESTURE_SHOW_PRESS:
+        // It's possible that a double-tap drag zoom (from ScaleGestureDetector)
+        // will start before the press gesture fires (from GestureDetector), in
+        // which case the press should simply be dropped.
+        if (pinch_event_sent_ || scroll_event_sent_)
+          return;
+      default:
+        break;
+    };
+
+    client_->OnGestureEvent(gesture);
+    GestureTouchUMAHistogram::RecordGestureEvent(gesture);
+  }
+
+  // ScaleGestureListener implementation.
+  virtual bool OnScaleBegin(const ScaleGestureDetector& detector,
+                            const MotionEvent& e) OVERRIDE {
+    if (ignore_multitouch_zoom_events_ && !detector.InDoubleTapMode())
+      return false;
+    return true;
+  }
+
+  virtual void OnScaleEnd(const ScaleGestureDetector& detector,
+                          const MotionEvent& e) OVERRIDE {
+    if (!pinch_event_sent_)
+      return;
+    Send(CreateGesture(ET_GESTURE_PINCH_END, e));
+  }
+
+  virtual bool OnScale(const ScaleGestureDetector& detector,
+                       const MotionEvent& e) OVERRIDE {
+    if (ignore_multitouch_zoom_events_ && !detector.InDoubleTapMode())
+      return false;
+    if (!pinch_event_sent_) {
+      Send(CreateGesture(ET_GESTURE_PINCH_BEGIN,
+                         e.GetId(),
+                         e.GetToolType(),
+                         detector.GetEventTime(),
+                         detector.GetFocusX(),
+                         detector.GetFocusY(),
+                         detector.GetFocusX() + e.GetRawOffsetX(),
+                         detector.GetFocusY() + e.GetRawOffsetY(),
+                         e.GetPointerCount(),
+                         GetBoundingBox(e, ET_GESTURE_PINCH_BEGIN),
+                         e.GetFlags()));
+    }
+
+    if (std::abs(detector.GetCurrentSpan() - detector.GetPreviousSpan()) <
+        config_.scale_gesture_detector_config.min_pinch_update_span_delta) {
+      return false;
+    }
+
+    float scale = detector.GetScaleFactor();
+    if (scale == 1)
+      return true;
+
+    if (detector.InDoubleTapMode()) {
+      // Relative changes in the double-tap scale factor computed by |detector|
+      // diminish as the touch moves away from the original double-tap focus.
+      // For historical reasons, Chrome has instead adopted a scale factor
+      // computation that is invariant to the focal distance, where
+      // the scale delta remains constant if the touch velocity is constant.
+      float dy =
+          (detector.GetCurrentSpanY() - detector.GetPreviousSpanY()) * 0.5f;
+      scale = std::pow(scale > 1 ? 1.0f + kDoubleTapDragZoomSpeed
+                                 : 1.0f - kDoubleTapDragZoomSpeed,
+                       std::abs(dy));
+    }
+    GestureEventDetails pinch_details(ET_GESTURE_PINCH_UPDATE);
+    pinch_details.set_scale(scale);
+    Send(CreateGesture(pinch_details,
+                       e.GetId(),
+                       e.GetToolType(),
+                       detector.GetEventTime(),
+                       detector.GetFocusX(),
+                       detector.GetFocusY(),
+                       detector.GetFocusX() + e.GetRawOffsetX(),
+                       detector.GetFocusY() + e.GetRawOffsetY(),
+                       e.GetPointerCount(),
+                       GetBoundingBox(e, pinch_details.type()),
+                       e.GetFlags()));
+    return true;
+  }
+
+  // GestureListener implementation.
+  virtual bool OnDown(const MotionEvent& e) OVERRIDE {
+    GestureEventDetails tap_details(ET_GESTURE_TAP_DOWN);
+    Send(CreateGesture(tap_details, e));
+
+    // Return true to indicate that we want to handle touch.
+    return true;
+  }
+
+  virtual bool OnScroll(const MotionEvent& e1,
+                        const MotionEvent& e2,
+                        float raw_distance_x,
+                        float raw_distance_y) OVERRIDE {
+    float distance_x = raw_distance_x;
+    float distance_y = raw_distance_y;
+    if (!scroll_event_sent_) {
+      // Remove the touch slop region from the first scroll event to avoid a
+      // jump.
+      double distance =
+          std::sqrt(distance_x * distance_x + distance_y * distance_y);
+      double epsilon = 1e-3;
+      if (distance > epsilon) {
+        double ratio =
+            std::max(0.,
+                     distance - config_.gesture_detector_config.touch_slop) /
+            distance;
+        distance_x *= ratio;
+        distance_y *= ratio;
+      }
+
+      // Note that scroll start hints are in distance traveled, where
+      // scroll deltas are in the opposite direction.
+      GestureEventDetails scroll_details(
+          ET_GESTURE_SCROLL_BEGIN, -raw_distance_x, -raw_distance_y);
+
+      // Use the co-ordinates from the touch down, as these co-ordinates are
+      // used to determine which layer the scroll should affect.
+      Send(CreateGesture(scroll_details,
+                         e2.GetId(),
+                         e2.GetToolType(),
+                         e2.GetEventTime(),
+                         e1.GetX(),
+                         e1.GetY(),
+                         e1.GetRawX(),
+                         e1.GetRawY(),
+                         e2.GetPointerCount(),
+                         GetBoundingBox(e2, scroll_details.type()),
+                         e2.GetFlags()));
+      DCHECK(scroll_event_sent_);
+    }
+
+    snap_scroll_controller_.UpdateSnapScrollMode(distance_x, distance_y);
+    if (snap_scroll_controller_.IsSnappingScrolls()) {
+      if (snap_scroll_controller_.IsSnapHorizontal())
+        distance_y = 0;
+      else
+        distance_x = 0;
+    }
+
+    if (distance_x || distance_y) {
+      GestureEventDetails scroll_details(
+          ET_GESTURE_SCROLL_UPDATE, -distance_x, -distance_y);
+      const gfx::RectF bounding_box = GetBoundingBox(e2, scroll_details.type());
+      const gfx::PointF center = bounding_box.CenterPoint();
+      const gfx::PointF raw_center =
+          center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY());
+      Send(CreateGesture(scroll_details,
+                         e2.GetId(),
+                         e2.GetToolType(),
+                         e2.GetEventTime(),
+                         center.x(),
+                         center.y(),
+                         raw_center.x(),
+                         raw_center.y(),
+                         e2.GetPointerCount(),
+                         bounding_box,
+                         e2.GetFlags()));
+    }
+
+    return true;
+  }
+
+  virtual bool OnFling(const MotionEvent& e1,
+                       const MotionEvent& e2,
+                       float velocity_x,
+                       float velocity_y) OVERRIDE {
+    if (snap_scroll_controller_.IsSnappingScrolls()) {
+      if (snap_scroll_controller_.IsSnapHorizontal()) {
+        velocity_y = 0;
+      } else {
+        velocity_x = 0;
+      }
+    }
+
+    if (!velocity_x && !velocity_y)
+      return true;
+
+    if (!scroll_event_sent_) {
+      // The native side needs a ET_GESTURE_SCROLL_BEGIN before
+      // ET_SCROLL_FLING_START to send the fling to the correct target.
+      // The distance traveled in one second is a reasonable scroll start hint.
+      GestureEventDetails scroll_details(
+          ET_GESTURE_SCROLL_BEGIN, velocity_x, velocity_y);
+      Send(CreateGesture(scroll_details, e2));
+    }
+
+    GestureEventDetails fling_details(
+        ET_SCROLL_FLING_START, velocity_x, velocity_y);
+    Send(CreateGesture(fling_details, e2));
+    return true;
+  }
+
+  virtual bool OnSwipe(const MotionEvent& e1,
+                       const MotionEvent& e2,
+                       float velocity_x,
+                       float velocity_y) OVERRIDE {
+    GestureEventDetails swipe_details(ET_GESTURE_SWIPE, velocity_x, velocity_y);
+    Send(CreateGesture(swipe_details, e2));
+    return true;
+  }
+
+  virtual bool OnTwoFingerTap(const MotionEvent& e1,
+                              const MotionEvent& e2) OVERRIDE {
+    // The location of the two finger tap event should be the location of the
+    // primary pointer.
+    GestureEventDetails two_finger_tap_details(
+        ET_GESTURE_TWO_FINGER_TAP, e1.GetTouchMajor(), e1.GetTouchMajor());
+    Send(CreateGesture(two_finger_tap_details,
+                       e2.GetId(),
+                       e2.GetToolType(),
+                       e2.GetEventTime(),
+                       e1.GetX(),
+                       e1.GetY(),
+                       e1.GetRawX(),
+                       e1.GetRawY(),
+                       e2.GetPointerCount(),
+                       GetBoundingBox(e2, two_finger_tap_details.type()),
+                       e2.GetFlags()));
+    return true;
+  }
+
+  virtual void OnShowPress(const MotionEvent& e) OVERRIDE {
+    GestureEventDetails show_press_details(ET_GESTURE_SHOW_PRESS);
+    show_press_event_sent_ = true;
+    Send(CreateGesture(show_press_details, e));
+  }
+
+  virtual bool OnSingleTapUp(const MotionEvent& e) OVERRIDE {
+    // This is a hack to address the issue where user hovers
+    // over a link for longer than double_tap_timeout_, then
+    // OnSingleTapConfirmed() is not triggered. But we still
+    // want to trigger the tap event at UP. So we override
+    // OnSingleTapUp() in this case. This assumes singleTapUp
+    // gets always called before singleTapConfirmed.
+    if (!ignore_single_tap_) {
+      if (e.GetEventTime() - current_down_time_ >
+          config_.gesture_detector_config.double_tap_timeout) {
+        return OnSingleTapConfirmed(e);
+      } else if (!IsDoubleTapEnabled() || config_.disable_click_delay) {
+        // If double-tap has been disabled, there is no need to wait
+        // for the double-tap timeout.
+        return OnSingleTapConfirmed(e);
+      } else {
+        // Notify Blink about this tapUp event anyway, when none of the above
+        // conditions applied.
+        Send(CreateTapGesture(ET_GESTURE_TAP_UNCONFIRMED, e));
+      }
+    }
+
+    if (e.GetAction() == MotionEvent::ACTION_UP &&
+        !current_longpress_time_.is_null() &&
+        !IsScaleGestureDetectionInProgress()) {
+      GestureEventDetails long_tap_details(ET_GESTURE_LONG_TAP);
+      Send(CreateGesture(long_tap_details, e));
+      return true;
+    }
+
+    return false;
+  }
+
+  // DoubleTapListener implementation.
+  virtual bool OnSingleTapConfirmed(const MotionEvent& e) OVERRIDE {
+    // Long taps in the edges of the screen have their events delayed by
+    // ContentViewHolder for tab swipe operations. As a consequence of the delay
+    // this method might be called after receiving the up event.
+    // These corner cases should be ignored.
+    if (ignore_single_tap_)
+      return true;
+
+    ignore_single_tap_ = true;
+
+    Send(CreateTapGesture(ET_GESTURE_TAP, e));
+    return true;
+  }
+
+  virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE {
+    return scale_gesture_detector_.OnDoubleTap(e);
+  }
+
+  virtual bool OnDoubleTapEvent(const MotionEvent& e) OVERRIDE {
+    switch (e.GetAction()) {
+      case MotionEvent::ACTION_DOWN:
+        gesture_detector_.set_longpress_enabled(false);
+        break;
+
+      case MotionEvent::ACTION_UP:
+        if (!IsPinchInProgress() && !IsScrollInProgress()) {
+          Send(CreateTapGesture(ET_GESTURE_DOUBLE_TAP, e));
+          return true;
+        }
+        break;
+
+      default:
+        break;
+    }
+    return false;
+  }
+
+  virtual void OnLongPress(const MotionEvent& e) OVERRIDE {
+    DCHECK(!IsDoubleTapInProgress());
+    SetIgnoreSingleTap(true);
+    GestureEventDetails long_press_details(ET_GESTURE_LONG_PRESS);
+    Send(CreateGesture(long_press_details, e));
+  }
+
+  GestureEventData CreateGesture(const GestureEventDetails& details,
+                                 int motion_event_id,
+                                 MotionEvent::ToolType primary_tool_type,
+                                 base::TimeTicks time,
+                                 float x,
+                                 float y,
+                                 float raw_x,
+                                 float raw_y,
+                                 size_t touch_point_count,
+                                 const gfx::RectF& bounding_box,
+                                 int flags) {
+    return GestureEventData(details,
+                            motion_event_id,
+                            primary_tool_type,
+                            time,
+                            x,
+                            y,
+                            raw_x,
+                            raw_y,
+                            touch_point_count,
+                            bounding_box,
+                            flags);
+  }
+
+  GestureEventData CreateGesture(EventType type,
+                                 int motion_event_id,
+                                 MotionEvent::ToolType primary_tool_type,
+                                 base::TimeTicks time,
+                                 float x,
+                                 float y,
+                                 float raw_x,
+                                 float raw_y,
+                                 size_t touch_point_count,
+                                 const gfx::RectF& bounding_box,
+                                 int flags) {
+    return GestureEventData(GestureEventDetails(type),
+                            motion_event_id,
+                            primary_tool_type,
+                            time,
+                            x,
+                            y,
+                            raw_x,
+                            raw_y,
+                            touch_point_count,
+                            bounding_box,
+                            flags);
+  }
+
+  GestureEventData CreateGesture(const GestureEventDetails& details,
+                                 const MotionEvent& event) {
+    return GestureEventData(details,
+                            event.GetId(),
+                            event.GetToolType(),
+                            event.GetEventTime(),
+                            event.GetX(),
+                            event.GetY(),
+                            event.GetRawX(),
+                            event.GetRawY(),
+                            event.GetPointerCount(),
+                            GetBoundingBox(event, details.type()),
+                            event.GetFlags());
+  }
+
+  GestureEventData CreateGesture(EventType type, const MotionEvent& event) {
+    return CreateGesture(GestureEventDetails(type), event);
+  }
+
+  GestureEventData CreateTapGesture(EventType type, const MotionEvent& event) {
+    // Set the tap count to 1 even for ET_GESTURE_DOUBLE_TAP, in order to be
+    // consistent with double tap behavior on a mobile viewport. See
+    // crbug.com/234986 for context.
+    GestureEventDetails details(type);
+    details.set_tap_count(1);
+    return CreateGesture(details, event);
+  }
+
+  gfx::RectF GetBoundingBox(const MotionEvent& event, EventType type) {
+    // Can't use gfx::RectF::Union, as it ignores touches with a radius of 0.
+    float left = std::numeric_limits<float>::max();
+    float top = std::numeric_limits<float>::max();
+    float right = -std::numeric_limits<float>::max();
+    float bottom = -std::numeric_limits<float>::max();
+    for (size_t i = 0; i < event.GetPointerCount(); ++i) {
+      float x, y, diameter;
+      // Only for the show press and tap events, the bounding box is calculated
+      // based on the touch start point and the maximum diameter before the
+      // show press event is sent.
+      if (type == ET_GESTURE_SHOW_PRESS || type == ET_GESTURE_TAP ||
+          type == ET_GESTURE_TAP_UNCONFIRMED) {
+        DCHECK_EQ(0U, i);
+        diameter = max_diameter_before_show_press_;
+        x = tap_down_point_.x();
+        y = tap_down_point_.y();
+      } else {
+        diameter = event.GetTouchMajor(i);
+        x = event.GetX(i);
+        y = event.GetY(i);
+      }
+      x = x - diameter / 2;
+      y = y - diameter / 2;
+      left = std::min(left, x);
+      right = std::max(right, x + diameter);
+      top = std::min(top, y);
+      bottom = std::max(bottom, y + diameter);
+    }
+    return gfx::RectF(left, top, right - left, bottom - top);
+  }
+
+  void SetDoubleTapEnabled(bool enabled) {
+    DCHECK(!IsDoubleTapInProgress());
+    gesture_detector_.SetDoubleTapListener(enabled ? this : NULL);
+  }
+
+  void SetMultiTouchZoomEnabled(bool enabled) {
+    // Note that returning false from |OnScaleBegin()| or |OnScale()| prevents
+    // the detector from emitting further scale updates for the current touch
+    // sequence. Thus, if multitouch events are enabled in the middle of a
+    // gesture, it will only take effect with the next gesture.
+    ignore_multitouch_zoom_events_ = !enabled;
+  }
+
+  bool IsDoubleTapInProgress() const {
+    return gesture_detector_.is_double_tapping() ||
+           (IsScaleGestureDetectionInProgress() && InDoubleTapMode());
+  }
+
+  bool IsScrollInProgress() const { return scroll_event_sent_; }
+
+  bool IsPinchInProgress() const { return pinch_event_sent_; }
+
+ private:
+  bool IsScaleGestureDetectionInProgress() const {
+    return scale_gesture_detector_.IsInProgress();
+  }
+
+  bool InDoubleTapMode() const {
+    return scale_gesture_detector_.InDoubleTapMode();
+  }
+
+  bool IsDoubleTapEnabled() const {
+    return gesture_detector_.has_doubletap_listener();
+  }
+
+  void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; }
+
+  const GestureProvider::Config config_;
+  GestureProviderClient* const client_;
+
+  GestureDetector gesture_detector_;
+  ScaleGestureDetector scale_gesture_detector_;
+  SnapScrollController snap_scroll_controller_;
+
+  base::TimeTicks current_down_time_;
+
+  // Keeps track of the current GESTURE_LONG_PRESS event. If a context menu is
+  // opened after a GESTURE_LONG_PRESS, this is used to insert a
+  // GESTURE_TAP_CANCEL for removing any ::active styling.
+  base::TimeTicks current_longpress_time_;
+
+  // Completely silence multi-touch (pinch) scaling events. Used in WebView when
+  // zoom support is turned off.
+  bool ignore_multitouch_zoom_events_;
+
+  // TODO(klobag): This is to avoid a bug in GestureDetector. With multi-touch,
+  // always_in_tap_region_ is not reset. So when the last finger is up,
+  // |OnSingleTapUp()| will be mistakenly fired.
+  bool ignore_single_tap_;
+
+  // Tracks whether {PINCH|SCROLL}_BEGIN events have been forwarded for the
+  // current touch sequence.
+  bool pinch_event_sent_;
+  bool scroll_event_sent_;
+
+  // Only track the maximum diameter before the show press event has been
+  // sent and a tap must still be possible for this touch sequence.
+  float max_diameter_before_show_press_;
+
+  gfx::PointF tap_down_point_;
+
+  // Tracks whether an ET_GESTURE_SHOW_PRESS event has been sent for this touch
+  // sequence.
+  bool show_press_event_sent_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl);
+};
+
+// GestureProvider
+
+GestureProvider::GestureProvider(const Config& config,
+                                 GestureProviderClient* client)
+    : double_tap_support_for_page_(true),
+      double_tap_support_for_platform_(true),
+      gesture_begin_end_types_enabled_(config.gesture_begin_end_types_enabled) {
+  DCHECK(client);
+  DCHECK(!config.min_gesture_bounds_length ||
+         !config.max_gesture_bounds_length ||
+         config.min_gesture_bounds_length <= config.max_gesture_bounds_length);
+  TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
+  gesture_listener_.reset(new GestureListenerImpl(config, client));
+  UpdateDoubleTapDetectionSupport();
+}
+
+GestureProvider::~GestureProvider() {
+}
+
+bool GestureProvider::OnTouchEvent(const MotionEvent& event) {
+  TRACE_EVENT1("input",
+               "GestureProvider::OnTouchEvent",
+               "action",
+               GetMotionEventActionName(event.GetAction()));
+
+  DCHECK_NE(0u, event.GetPointerCount());
+
+  if (!CanHandle(event))
+    return false;
+
+  OnTouchEventHandlingBegin(event);
+  gesture_listener_->OnTouchEvent(event);
+  OnTouchEventHandlingEnd(event);
+  uma_histogram_.RecordTouchEvent(event);
+  return true;
+}
+
+void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled) {
+  gesture_listener_->SetMultiTouchZoomEnabled(enabled);
+}
+
+void GestureProvider::SetDoubleTapSupportForPlatformEnabled(bool enabled) {
+  if (double_tap_support_for_platform_ == enabled)
+    return;
+  double_tap_support_for_platform_ = enabled;
+  UpdateDoubleTapDetectionSupport();
+}
+
+void GestureProvider::SetDoubleTapSupportForPageEnabled(bool enabled) {
+  if (double_tap_support_for_page_ == enabled)
+    return;
+  double_tap_support_for_page_ = enabled;
+  UpdateDoubleTapDetectionSupport();
+}
+
+bool GestureProvider::IsScrollInProgress() const {
+  return gesture_listener_->IsScrollInProgress();
+}
+
+bool GestureProvider::IsPinchInProgress() const {
+  return gesture_listener_->IsPinchInProgress();
+}
+
+bool GestureProvider::IsDoubleTapInProgress() const {
+  return gesture_listener_->IsDoubleTapInProgress();
+}
+
+bool GestureProvider::CanHandle(const MotionEvent& event) const {
+  // Aura requires one cancel event per touch point, whereas Android requires
+  // one cancel event per touch sequence. Thus we need to allow extra cancel
+  // events.
+  return current_down_event_ || event.GetAction() == MotionEvent::ACTION_DOWN ||
+         event.GetAction() == MotionEvent::ACTION_CANCEL;
+}
+
+void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent& event) {
+  switch (event.GetAction()) {
+    case MotionEvent::ACTION_DOWN:
+      current_down_event_ = event.Clone();
+      if (gesture_begin_end_types_enabled_)
+        gesture_listener_->Send(
+            gesture_listener_->CreateGesture(ET_GESTURE_BEGIN, event));
+      break;
+    case MotionEvent::ACTION_POINTER_DOWN:
+      if (gesture_begin_end_types_enabled_) {
+        const int action_index = event.GetActionIndex();
+        gesture_listener_->Send(gesture_listener_->CreateGesture(
+            ET_GESTURE_BEGIN,
+            event.GetId(),
+            event.GetToolType(),
+            event.GetEventTime(),
+            event.GetX(action_index),
+            event.GetY(action_index),
+            event.GetRawX(action_index),
+            event.GetRawY(action_index),
+            event.GetPointerCount(),
+            gesture_listener_->GetBoundingBox(event, ET_GESTURE_BEGIN),
+            event.GetFlags()));
+      }
+      break;
+    case MotionEvent::ACTION_POINTER_UP:
+    case MotionEvent::ACTION_UP:
+    case MotionEvent::ACTION_CANCEL:
+    case MotionEvent::ACTION_MOVE:
+      break;
+  }
+}
+
+void GestureProvider::OnTouchEventHandlingEnd(const MotionEvent& event) {
+  switch (event.GetAction()) {
+    case MotionEvent::ACTION_UP:
+    case MotionEvent::ACTION_CANCEL: {
+      if (gesture_begin_end_types_enabled_)
+        gesture_listener_->Send(
+            gesture_listener_->CreateGesture(ET_GESTURE_END, event));
+
+      current_down_event_.reset();
+
+      UpdateDoubleTapDetectionSupport();
+      break;
+    }
+    case MotionEvent::ACTION_POINTER_UP:
+      if (gesture_begin_end_types_enabled_)
+        gesture_listener_->Send(
+            gesture_listener_->CreateGesture(ET_GESTURE_END, event));
+      break;
+    case MotionEvent::ACTION_DOWN:
+    case MotionEvent::ACTION_POINTER_DOWN:
+    case MotionEvent::ACTION_MOVE:
+      break;
+  }
+}
+
+void GestureProvider::UpdateDoubleTapDetectionSupport() {
+  // The GestureDetector requires that any provided DoubleTapListener remain
+  // attached to it for the duration of a touch sequence. Defer any potential
+  // null'ing of the listener until the sequence has ended.
+  if (current_down_event_)
+    return;
+
+  const bool double_tap_enabled =
+      double_tap_support_for_page_ && double_tap_support_for_platform_;
+  gesture_listener_->SetDoubleTapEnabled(double_tap_enabled);
+}
+
+}  //  namespace ui
diff --git a/ui/events/gesture_detection/gesture_provider.h b/ui/events/gesture_detection/gesture_provider.h
new file mode 100644
index 0000000..7474967
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider.h
@@ -0,0 +1,117 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_detector.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+#include "ui/events/gesture_detection/gesture_touch_uma_histogram.h"
+#include "ui/events/gesture_detection/scale_gesture_detector.h"
+#include "ui/events/gesture_detection/snap_scroll_controller.h"
+#include "ui/gfx/display.h"
+
+namespace ui {
+
+class GESTURE_DETECTION_EXPORT GestureProviderClient {
+ public:
+  virtual ~GestureProviderClient() {}
+  virtual void OnGestureEvent(const GestureEventData& gesture) = 0;
+};
+
+// Given a stream of |MotionEvent|'s, provides gesture detection and gesture
+// event dispatch.
+class GESTURE_DETECTION_EXPORT GestureProvider {
+ public:
+  struct GESTURE_DETECTION_EXPORT Config {
+    Config();
+    ~Config();
+    gfx::Display display;
+    GestureDetector::Config gesture_detector_config;
+    ScaleGestureDetector::Config scale_gesture_detector_config;
+
+    // If |disable_click_delay| is true and double-tap support is disabled,
+    // there will be no delay before tap events. When double-tap support is
+    // enabled, there will always be a delay before a tap event is fired, in
+    // order to allow the double tap gesture to occur without firing any tap
+    // events.
+    bool disable_click_delay;
+
+    // If |gesture_begin_end_types_enabled| is true, fire an ET_GESTURE_BEGIN
+    // event for every added touch point, and an ET_GESTURE_END event for every
+    // removed touch point. This requires one ACTION_CANCEL event to be sent per
+    // touch point, which only occurs on Aura. Defaults to false.
+    bool gesture_begin_end_types_enabled;
+
+    // The min and max size (both length and width, in dips) of the generated
+    // bounding box for all gesture types. This is useful for touch streams
+    // that may report zero or unreasonably small or large touch sizes.
+    // Note that these bounds are only applied for touch or unknown tool types;
+    // mouse and stylus-derived gestures will not be affected.
+    // Both values default to 0 (disabled).
+    float min_gesture_bounds_length;
+    float max_gesture_bounds_length;
+  };
+
+  GestureProvider(const Config& config, GestureProviderClient* client);
+  ~GestureProvider();
+
+  // Handle the incoming MotionEvent, returning false if the event could not
+  // be handled.
+  bool OnTouchEvent(const MotionEvent& event);
+
+  // Update whether multi-touch pinch zoom is supported by the platform.
+  void SetMultiTouchZoomSupportEnabled(bool enabled);
+
+  // Update whether double-tap gestures are supported by the platform.
+  void SetDoubleTapSupportForPlatformEnabled(bool enabled);
+
+  // Update whether double-tap gesture detection should be suppressed, e.g.,
+  // if the page scale is fixed or the page has a mobile viewport. This disables
+  // the tap delay, allowing rapid and responsive single-tap gestures.
+  void SetDoubleTapSupportForPageEnabled(bool enabled);
+
+  // Whether a scroll gesture is in-progress.
+  bool IsScrollInProgress() const;
+
+  // Whether a pinch gesture is in-progress (i.e. a pinch update has been
+  // forwarded and detection is still active).
+  bool IsPinchInProgress() const;
+
+  // Whether a double-tap gesture is in-progress (either double-tap or
+  // double-tap drag zoom).
+  bool IsDoubleTapInProgress() const;
+
+  // May be NULL if there is no currently active touch sequence.
+  const ui::MotionEvent* current_down_event() const {
+    return current_down_event_.get();
+  }
+
+ private:
+  bool CanHandle(const MotionEvent& event) const;
+  void OnTouchEventHandlingBegin(const MotionEvent& event);
+  void OnTouchEventHandlingEnd(const MotionEvent& event);
+  void UpdateDoubleTapDetectionSupport();
+
+  class GestureListenerImpl;
+  scoped_ptr<GestureListenerImpl> gesture_listener_;
+
+  scoped_ptr<MotionEvent> current_down_event_;
+
+  // Logs information on touch and gesture events.
+  GestureTouchUMAHistogram uma_histogram_;
+
+  // Whether double-tap gesture detection is currently supported.
+  bool double_tap_support_for_page_;
+  bool double_tap_support_for_platform_;
+
+  const bool gesture_begin_end_types_enabled_;
+};
+
+}  //  namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_
diff --git a/ui/events/gesture_detection/gesture_provider_unittest.cc b/ui/events/gesture_detection/gesture_provider_unittest.cc
new file mode 100644
index 0000000..a25a0b5
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_provider_unittest.cc
@@ -0,0 +1,2502 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+#include "ui/events/gesture_detection/gesture_provider.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/test/mock_motion_event.h"
+#include "ui/gfx/geometry/point_f.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::test::MockMotionEvent;
+
+namespace ui {
+namespace {
+
+const float kFakeCoordX = 42.f;
+const float kFakeCoordY = 24.f;
+const TimeDelta kOneSecond = TimeDelta::FromSeconds(1);
+const TimeDelta kOneMicrosecond = TimeDelta::FromMicroseconds(1);
+const TimeDelta kDeltaTimeForFlingSequences = TimeDelta::FromMilliseconds(5);
+const float kMockTouchRadius = MockMotionEvent::TOUCH_MAJOR / 2;
+const float kMaxTwoFingerTapSeparation = 300;
+
+GestureProvider::Config CreateDefaultConfig() {
+  GestureProvider::Config sConfig;
+  // The longpress timeout is non-zero only to indicate ordering with respect to
+  // the showpress timeout.
+  sConfig.gesture_detector_config.showpress_timeout = base::TimeDelta();
+  sConfig.gesture_detector_config.longpress_timeout = kOneMicrosecond;
+
+  // A valid doubletap timeout should always be non-zero. The value is used not
+  // only to trigger the timeout that confirms the tap event, but also to gate
+  // whether the second tap is in fact a double-tap (using a strict inequality
+  // between times for the first up and the second down events). We use 4
+  // microseconds simply to allow several intermediate events to occur before
+  // the second tap at microsecond intervals.
+  sConfig.gesture_detector_config.double_tap_timeout = kOneMicrosecond * 4;
+  sConfig.gesture_detector_config.double_tap_min_time = kOneMicrosecond * 2;
+  return sConfig;
+}
+
+gfx::RectF BoundsForSingleMockTouchAtLocation(float x, float y) {
+  float diameter = MockMotionEvent::TOUCH_MAJOR;
+  return gfx::RectF(x - diameter / 2, y - diameter / 2, diameter, diameter);
+}
+
+}  // namespace
+
+class GestureProviderTest : public testing::Test, public GestureProviderClient {
+ public:
+  GestureProviderTest() {}
+  virtual ~GestureProviderTest() {}
+
+  static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
+                                           MotionEvent::Action action,
+                                           float x,
+                                           float y) {
+    return MockMotionEvent(action, event_time, x, y);
+  }
+
+  static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
+                                           MotionEvent::Action action,
+                                           float x0,
+                                           float y0,
+                                           float x1,
+                                           float y1) {
+    return MockMotionEvent(action, event_time, x0, y0, x1, y1);
+  }
+
+  static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
+                                           MotionEvent::Action action,
+                                           float x0,
+                                           float y0,
+                                           float x1,
+                                           float y1,
+                                           float x2,
+                                           float y2) {
+    return MockMotionEvent(action, event_time, x0, y0, x1, y1, x2, y2);
+  }
+
+  static MockMotionEvent ObtainMotionEvent(
+      base::TimeTicks event_time,
+      MotionEvent::Action action,
+      const std::vector<gfx::PointF>& positions) {
+    switch (positions.size()) {
+      case 1:
+        return MockMotionEvent(
+            action, event_time, positions[0].x(), positions[0].y());
+      case 2:
+        return MockMotionEvent(action,
+                               event_time,
+                               positions[0].x(),
+                               positions[0].y(),
+                               positions[1].x(),
+                               positions[1].y());
+      case 3:
+        return MockMotionEvent(action,
+                               event_time,
+                               positions[0].x(),
+                               positions[0].y(),
+                               positions[1].x(),
+                               positions[1].y(),
+                               positions[2].x(),
+                               positions[2].y());
+      default:
+        CHECK(false) << "MockMotionEvent only supports 1-3 pointers";
+        return MockMotionEvent();
+    }
+  }
+
+  static MockMotionEvent ObtainMotionEvent(base::TimeTicks event_time,
+                                           MotionEvent::Action action) {
+    return ObtainMotionEvent(event_time, action, kFakeCoordX, kFakeCoordY);
+  }
+
+  // Test
+  virtual void SetUp() OVERRIDE { SetUpWithConfig(GetDefaultConfig()); }
+
+  virtual void TearDown() OVERRIDE {
+    gestures_.clear();
+    gesture_provider_.reset();
+  }
+
+  // GestureProviderClient
+  virtual void OnGestureEvent(const GestureEventData& gesture) OVERRIDE {
+    if (gesture.type() == ET_GESTURE_SCROLL_BEGIN)
+      active_scroll_begin_event_.reset(new GestureEventData(gesture));
+    gestures_.push_back(gesture);
+  }
+
+  void SetUpWithConfig(const GestureProvider::Config& config) {
+    gesture_provider_.reset(new GestureProvider(config, this));
+    gesture_provider_->SetMultiTouchZoomSupportEnabled(false);
+  }
+
+  void ResetGestureDetection() {
+    CancelActiveTouchSequence();
+    gestures_.clear();
+  }
+  bool CancelActiveTouchSequence() {
+    if (!gesture_provider_->current_down_event())
+      return false;
+    return gesture_provider_->OnTouchEvent(
+        *gesture_provider_->current_down_event()->Cancel());
+  }
+
+  bool HasReceivedGesture(EventType type) const {
+    for (size_t i = 0; i < gestures_.size(); ++i) {
+      if (gestures_[i].type() == type)
+        return true;
+    }
+    return false;
+  }
+
+  const GestureEventData& GetMostRecentGestureEvent() const {
+    EXPECT_FALSE(gestures_.empty());
+    return gestures_.back();
+  }
+
+  EventType GetMostRecentGestureEventType() const {
+    EXPECT_FALSE(gestures_.empty());
+    return gestures_.back().type();
+  }
+
+  size_t GetReceivedGestureCount() const { return gestures_.size(); }
+
+  const GestureEventData& GetReceivedGesture(size_t index) const {
+    EXPECT_LT(index, GetReceivedGestureCount());
+    return gestures_[index];
+  }
+
+  const GestureEventData* GetActiveScrollBeginEvent() const {
+    return active_scroll_begin_event_ ? active_scroll_begin_event_.get() : NULL;
+  }
+
+  const GestureProvider::Config& GetDefaultConfig() const {
+    static GestureProvider::Config sConfig = CreateDefaultConfig();
+    return sConfig;
+  }
+
+  float GetTouchSlop() const {
+    return GetDefaultConfig().gesture_detector_config.touch_slop;
+  }
+
+  float GetMinScalingSpan() const {
+    return GetDefaultConfig().scale_gesture_detector_config.min_scaling_span;
+  }
+
+  float GetMinSwipeVelocity() const {
+    return GetDefaultConfig().gesture_detector_config.minimum_swipe_velocity;
+  }
+
+  base::TimeDelta GetLongPressTimeout() const {
+    return GetDefaultConfig().gesture_detector_config.longpress_timeout;
+  }
+
+  base::TimeDelta GetShowPressTimeout() const {
+    return GetDefaultConfig().gesture_detector_config.showpress_timeout;
+  }
+
+  base::TimeDelta GetDoubleTapTimeout() const {
+    return GetDefaultConfig().gesture_detector_config.double_tap_timeout;
+  }
+
+  base::TimeDelta GetDoubleTapMinTime() const {
+    return GetDefaultConfig().gesture_detector_config.double_tap_min_time;
+  }
+
+  base::TimeDelta GetValidDoubleTapDelay() const {
+    return (GetDoubleTapTimeout() + GetDoubleTapMinTime()) / 2;
+  }
+
+  void EnableBeginEndTypes() {
+    GestureProvider::Config config = GetDefaultConfig();
+    config.gesture_begin_end_types_enabled = true;
+    SetUpWithConfig(config);
+  }
+
+  void EnableSwipe() {
+    GestureProvider::Config config = GetDefaultConfig();
+    config.gesture_detector_config.swipe_enabled = true;
+    SetUpWithConfig(config);
+  }
+
+  void EnableTwoFingerTap(float max_distance_for_two_finger_tap,
+                          base::TimeDelta two_finger_tap_timeout) {
+    GestureProvider::Config config = GetDefaultConfig();
+    config.gesture_detector_config.two_finger_tap_enabled = true;
+    config.gesture_detector_config.two_finger_tap_max_separation =
+        max_distance_for_two_finger_tap;
+    config.gesture_detector_config.two_finger_tap_timeout =
+        two_finger_tap_timeout;
+    SetUpWithConfig(config);
+  }
+
+  void SetMinPinchUpdateSpanDelta(float min_pinch_update_span_delta) {
+    GestureProvider::Config config = GetDefaultConfig();
+    config.scale_gesture_detector_config.min_pinch_update_span_delta =
+        min_pinch_update_span_delta;
+    SetUpWithConfig(config);
+  }
+
+  void SetMinMaxGestureBoundsLength(float min_gesture_bound_length,
+                                    float max_gesture_bound_length) {
+    GestureProvider::Config config = GetDefaultConfig();
+    config.min_gesture_bounds_length = min_gesture_bound_length;
+    config.max_gesture_bounds_length = max_gesture_bound_length;
+    SetUpWithConfig(config);
+  }
+
+  void SetShowPressAndLongPressTimeout(base::TimeDelta showpress_timeout,
+                                       base::TimeDelta longpress_timeout) {
+    GestureProvider::Config config = GetDefaultConfig();
+    config.gesture_detector_config.showpress_timeout = showpress_timeout;
+    config.gesture_detector_config.longpress_timeout = longpress_timeout;
+    SetUpWithConfig(config);
+  }
+
+  bool HasDownEvent() const { return gesture_provider_->current_down_event(); }
+
+ protected:
+  void CheckScrollEventSequenceForEndActionType(
+      MotionEvent::Action end_action_type) {
+    base::TimeTicks event_time = base::TimeTicks::Now();
+    const float scroll_to_x = kFakeCoordX + 100;
+    const float scroll_to_y = kFakeCoordY + 100;
+    int motion_event_id = 0;
+    int motion_event_flags = EF_SHIFT_DOWN | EF_CAPS_LOCK_DOWN;
+
+    MockMotionEvent event =
+        ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+    event.set_id(++motion_event_id);
+    event.set_flags(motion_event_flags);
+
+    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+
+    event = ObtainMotionEvent(event_time + kOneSecond,
+                              MotionEvent::ACTION_MOVE,
+                              scroll_to_x,
+                              scroll_to_y);
+    event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+    event.set_id(++motion_event_id);
+    event.set_flags(motion_event_flags);
+
+    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+    EXPECT_TRUE(gesture_provider_->IsScrollInProgress());
+    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+    EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+    EXPECT_EQ(event.GetToolType(0),
+              GetMostRecentGestureEvent().primary_tool_type);
+    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(scroll_to_x, scroll_to_y),
+              GetMostRecentGestureEvent().details.bounding_box());
+    ASSERT_EQ(3U, GetReceivedGestureCount()) << "Only TapDown, "
+                                                "ScrollBegin and ScrollBy "
+                                                "should have been sent";
+
+    EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
+    EXPECT_EQ(motion_event_id, GetReceivedGesture(1).motion_event_id);
+    EXPECT_EQ(event_time + kOneSecond, GetReceivedGesture(1).time)
+        << "ScrollBegin should have the time of the ACTION_MOVE";
+
+    event = ObtainMotionEvent(
+        event_time + kOneSecond, end_action_type, scroll_to_x, scroll_to_y);
+    event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+    event.set_id(++motion_event_id);
+
+    gesture_provider_->OnTouchEvent(event);
+    EXPECT_FALSE(gesture_provider_->IsScrollInProgress());
+    EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_END));
+    EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+    EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+    EXPECT_EQ(event.GetToolType(0),
+              GetMostRecentGestureEvent().primary_tool_type);
+    EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+    EXPECT_EQ(BoundsForSingleMockTouchAtLocation(scroll_to_x, scroll_to_y),
+              GetMostRecentGestureEvent().details.bounding_box());
+  }
+
+  void OneFingerSwipe(float vx, float vy) {
+    std::vector<gfx::Vector2dF> velocities;
+    velocities.push_back(gfx::Vector2dF(vx, vy));
+    MultiFingerSwipe(velocities);
+  }
+
+  void TwoFingerSwipe(float vx0, float vy0, float vx1, float vy1) {
+    std::vector<gfx::Vector2dF> velocities;
+    velocities.push_back(gfx::Vector2dF(vx0, vy0));
+    velocities.push_back(gfx::Vector2dF(vx1, vy1));
+    MultiFingerSwipe(velocities);
+  }
+
+  void ThreeFingerSwipe(float vx0,
+                        float vy0,
+                        float vx1,
+                        float vy1,
+                        float vx2,
+                        float vy2) {
+    std::vector<gfx::Vector2dF> velocities;
+    velocities.push_back(gfx::Vector2dF(vx0, vy0));
+    velocities.push_back(gfx::Vector2dF(vx1, vy1));
+    velocities.push_back(gfx::Vector2dF(vx2, vy2));
+    MultiFingerSwipe(velocities);
+  }
+
+  void MultiFingerSwipe(std::vector<gfx::Vector2dF> velocities) {
+    ASSERT_GT(velocities.size(), 0U);
+
+    base::TimeTicks event_time = base::TimeTicks::Now();
+
+    std::vector<gfx::PointF> positions(velocities.size());
+    for (size_t i = 0; i < positions.size(); ++i)
+      positions[i] = gfx::PointF(kFakeCoordX * (i + 1), kFakeCoordY * (i + 1));
+
+    float dt = kDeltaTimeForFlingSequences.InSecondsF();
+
+    // Each pointer down should be a separate event.
+    for (size_t i = 0; i < positions.size(); ++i) {
+      const size_t pointer_count = i + 1;
+      std::vector<gfx::PointF> event_positions(pointer_count);
+      event_positions.assign(positions.begin(),
+                             positions.begin() + pointer_count);
+      MockMotionEvent event =
+          ObtainMotionEvent(event_time,
+                            pointer_count > 1 ? MotionEvent::ACTION_POINTER_DOWN
+                                              : MotionEvent::ACTION_DOWN,
+                            event_positions);
+      EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+    }
+
+    for (size_t i = 0; i < positions.size(); ++i)
+      positions[i] += gfx::ScaleVector2d(velocities[i], dt);
+    MockMotionEvent event =
+        ObtainMotionEvent(event_time + kDeltaTimeForFlingSequences,
+                          MotionEvent::ACTION_MOVE,
+                          positions);
+    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+    for (size_t i = 0; i < positions.size(); ++i)
+      positions[i] += gfx::ScaleVector2d(velocities[i], dt);
+    event = ObtainMotionEvent(event_time + 2 * kDeltaTimeForFlingSequences,
+                              MotionEvent::ACTION_MOVE,
+                              positions);
+    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+    event = ObtainMotionEvent(event_time + 2 * kDeltaTimeForFlingSequences,
+                              MotionEvent::ACTION_POINTER_UP,
+                              positions);
+    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  }
+
+  static void RunTasksAndWait(base::TimeDelta delay) {
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE, base::MessageLoop::QuitClosure(), delay);
+    base::MessageLoop::current()->Run();
+  }
+
+  std::vector<GestureEventData> gestures_;
+  scoped_ptr<GestureProvider> gesture_provider_;
+  scoped_ptr<GestureEventData> active_scroll_begin_event_;
+  base::MessageLoopForUI message_loop_;
+};
+
+// Verify that a DOWN followed shortly by an UP will trigger a single tap.
+TEST_F(GestureProviderTest, GestureTap) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  int motion_event_id = 0;
+  int motion_event_flags = EF_CONTROL_DOWN | EF_ALT_DOWN;
+
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(event.GetToolType(0),
+            GetMostRecentGestureEvent().primary_tool_type);
+  EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP);
+  event.SetToolType(0, MotionEvent::TOOL_TYPE_FINGER);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+  // Ensure tap details have been set.
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
+  EXPECT_EQ(event.GetToolType(0),
+            GetMostRecentGestureEvent().primary_tool_type);
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+}
+
+// Verify that a DOWN followed shortly by an UP will trigger
+// a ET_GESTURE_TAP_UNCONFIRMED event if double-tap is enabled.
+TEST_F(GestureProviderTest, GestureTapWithDelay) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  int motion_event_id = 0;
+  int motion_event_flags = EF_CONTROL_DOWN | EF_ALT_DOWN | EF_CAPS_LOCK_DOWN;
+
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  // Ensure tap details have been set.
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_TAP));
+  RunTasksAndWait(GetDoubleTapTimeout());
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_TAP));
+}
+
+// Verify that a DOWN followed by a MOVE will trigger fling (but not LONG).
+TEST_F(GestureProviderTest, GestureFlingAndCancelLongPress) {
+  base::TimeTicks event_time = TimeTicks::Now();
+  base::TimeDelta delta_time = kDeltaTimeForFlingSequences;
+  int motion_event_id = 0;
+  int motion_event_flags = EF_ALT_DOWN;
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(event_time + delta_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX * 10,
+                            kFakeCoordY * 10);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + delta_time * 2,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX * 10,
+                            kFakeCoordY * 10);
+  event.set_id(++motion_event_id);
+  event.set_flags(motion_event_flags);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(motion_event_flags, GetMostRecentGestureEvent().flags);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS));
+  EXPECT_EQ(
+      BoundsForSingleMockTouchAtLocation(kFakeCoordX * 10, kFakeCoordY * 10),
+      GetMostRecentGestureEvent().details.bounding_box());
+}
+
+// Verify that for a normal scroll the following events are sent:
+// - ET_GESTURE_SCROLL_BEGIN
+// - ET_GESTURE_SCROLL_UPDATE
+// - ET_GESTURE_SCROLL_END
+TEST_F(GestureProviderTest, ScrollEventActionUpSequence) {
+  CheckScrollEventSequenceForEndActionType(MotionEvent::ACTION_UP);
+}
+
+// Verify that for a cancelled scroll the following events are sent:
+// - ET_GESTURE_SCROLL_BEGIN
+// - ET_GESTURE_SCROLL_UPDATE
+// - ET_GESTURE_SCROLL_END
+TEST_F(GestureProviderTest, ScrollEventActionCancelSequence) {
+  CheckScrollEventSequenceForEndActionType(MotionEvent::ACTION_CANCEL);
+}
+
+// Verify that for a normal fling (fling after scroll) the following events are
+// sent:
+// - ET_GESTURE_SCROLL_BEGIN
+// - ET_SCROLL_FLING_START
+TEST_F(GestureProviderTest, FlingEventSequence) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  base::TimeDelta delta_time = kDeltaTimeForFlingSequences;
+  int motion_event_id = 0;
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.set_id(++motion_event_id);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + delta_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX * 5,
+                            kFakeCoordY * 5);
+  event.set_id(++motion_event_id);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(gesture_provider_->IsScrollInProgress());
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  ASSERT_EQ(3U, GetReceivedGestureCount());
+  ASSERT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
+  EXPECT_EQ(motion_event_id, GetReceivedGesture(1).motion_event_id);
+
+  // We don't want to take a dependency here on exactly how hints are calculated
+  // for a fling (eg. may depend on velocity), so just validate the direction.
+  int hint_x = GetReceivedGesture(1).details.scroll_x_hint();
+  int hint_y = GetReceivedGesture(1).details.scroll_y_hint();
+  EXPECT_TRUE(hint_x > 0 && hint_y > 0 && hint_x > hint_y)
+      << "ScrollBegin hint should be in positive X axis";
+
+  event = ObtainMotionEvent(event_time + delta_time * 2,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX * 10,
+                            kFakeCoordY * 10);
+  event.set_id(++motion_event_id);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(gesture_provider_->IsScrollInProgress());
+  EXPECT_EQ(ET_SCROLL_FLING_START, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_END));
+  EXPECT_EQ(event_time + delta_time * 2, GetMostRecentGestureEvent().time)
+      << "FlingStart should have the time of the ACTION_UP";
+}
+
+TEST_F(GestureProviderTest, GestureCancelledWhenWindowFocusLost) {
+  const base::TimeTicks event_time = TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+
+  RunTasksAndWait(GetLongPressTimeout() + GetShowPressTimeout() +
+                  kOneMicrosecond);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SHOW_PRESS));
+  EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
+
+  // The long press triggers window focus loss by opening a context menu.
+  EXPECT_TRUE(CancelActiveTouchSequence());
+  EXPECT_FALSE(HasDownEvent());
+
+  // A final ACTION_UP should have no effect.
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_UP);
+  EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));
+}
+
+TEST_F(GestureProviderTest, NoTapAfterScrollBegins) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 50,
+                            kFakeCoordY + 50);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+
+  event = ObtainMotionEvent(event_time + kOneSecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX + 50,
+                            kFakeCoordY + 50);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
+}
+
+TEST_F(GestureProviderTest, DoubleTap) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event_time += GetValidDoubleTapDelay();
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  // Moving a very small amount of distance should not trigger the double tap
+  // drag zoom mode.
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 1);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY + 1);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  const GestureEventData& double_tap = GetMostRecentGestureEvent();
+  EXPECT_EQ(ET_GESTURE_DOUBLE_TAP, double_tap.type());
+  // Ensure tap details have been set.
+  EXPECT_EQ(10, double_tap.details.bounding_box().width());
+  EXPECT_EQ(10, double_tap.details.bounding_box().height());
+  EXPECT_EQ(1, double_tap.details.tap_count());
+}
+
+TEST_F(GestureProviderTest, DoubleTapDragZoomBasic) {
+  const base::TimeTicks down_time_1 = TimeTicks::Now();
+  const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(
+      down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  ASSERT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 100),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  ASSERT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 200),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  ASSERT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_GT(1.f, GetMostRecentGestureEvent().details.scale());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY + 100),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 4,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY - 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_END));
+  EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY - 200),
+            GetMostRecentGestureEvent().details.bounding_box());
+}
+
+// Generate a scroll gesture and verify that the resulting scroll motion event
+// has both absolute and relative position information.
+TEST_F(GestureProviderTest, ScrollUpdateValues) {
+  const float delta_x = 16;
+  const float delta_y = 84;
+  const float raw_offset_x = 17.3f;
+  const float raw_offset_y = 13.7f;
+
+  const base::TimeTicks event_time = TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  // Move twice so that we get two ET_GESTURE_SCROLL_UPDATE events and can
+  // compare the relative and absolute coordinates.
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX - delta_x / 2,
+                            kFakeCoordY - delta_y / 2);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX - delta_x,
+                            kFakeCoordY - delta_y);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  // Make sure the reported gesture event has all the expected details.
+  ASSERT_LT(0U, GetReceivedGestureCount());
+  GestureEventData gesture = GetMostRecentGestureEvent();
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, gesture.type());
+  EXPECT_EQ(event_time + kOneMicrosecond * 2, gesture.time);
+  EXPECT_EQ(kFakeCoordX - delta_x, gesture.x);
+  EXPECT_EQ(kFakeCoordY - delta_y, gesture.y);
+  EXPECT_EQ(kFakeCoordX - delta_x + raw_offset_x, gesture.raw_x);
+  EXPECT_EQ(kFakeCoordY - delta_y + raw_offset_y, gesture.raw_y);
+  EXPECT_EQ(1, gesture.details.touch_points());
+
+  // No horizontal delta because of snapping.
+  EXPECT_EQ(0, gesture.details.scroll_x());
+  EXPECT_EQ(-delta_y / 2, gesture.details.scroll_y());
+}
+
+// Verify that fractional scroll deltas are rounded as expected and that
+// fractional scrolling doesn't break scroll snapping.
+TEST_F(GestureProviderTest, FractionalScroll) {
+  const float delta_x = 0.4f;
+  const float delta_y = 5.2f;
+
+  const base::TimeTicks event_time = TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  // Skip past the touch slop and move back.
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  // Now move up slowly, mostly vertically but with a (fractional) bit of
+  // horizontal motion.
+  for(int i = 1; i <= 10; i++) {
+    event = ObtainMotionEvent(event_time + kOneMicrosecond * i,
+                              MotionEvent::ACTION_MOVE,
+                              kFakeCoordX + delta_x * i,
+                              kFakeCoordY + delta_y * i);
+    EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+    ASSERT_LT(0U, GetReceivedGestureCount());
+    GestureEventData gesture = GetMostRecentGestureEvent();
+    EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, gesture.type());
+    EXPECT_EQ(event_time + kOneMicrosecond * i, gesture.time);
+    EXPECT_EQ(1, gesture.details.touch_points());
+
+    // Verify that the event co-ordinates are still the precise values we
+    // supplied.
+    EXPECT_EQ(kFakeCoordX + delta_x * i, gesture.x);
+    EXPECT_FLOAT_EQ(kFakeCoordY + delta_y * i, gesture.y);
+
+    // Verify that we're scrolling vertically by the expected amount
+    // (modulo rounding).
+    EXPECT_GE(gesture.details.scroll_y(), (int)delta_y);
+    EXPECT_LE(gesture.details.scroll_y(), ((int)delta_y) + 1);
+
+    // And that there has been no horizontal motion at all.
+    EXPECT_EQ(0, gesture.details.scroll_x());
+  }
+}
+
+// Generate a scroll gesture and verify that the resulting scroll begin event
+// has the expected hint values.
+TEST_F(GestureProviderTest, ScrollBeginValues) {
+  const float delta_x = 13;
+  const float delta_y = 89;
+
+  const base::TimeTicks event_time = TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  // Move twice such that the first event isn't sufficient to start
+  // scrolling on it's own.
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 2,
+                            kFakeCoordY + 1);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(gesture_provider_->IsScrollInProgress());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + delta_x,
+                            kFakeCoordY + delta_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(gesture_provider_->IsScrollInProgress());
+
+  const GestureEventData* scroll_begin_gesture = GetActiveScrollBeginEvent();
+  ASSERT_TRUE(!!scroll_begin_gesture);
+  EXPECT_EQ(delta_x, scroll_begin_gesture->details.scroll_x_hint());
+  EXPECT_EQ(delta_y, scroll_begin_gesture->details.scroll_y_hint());
+}
+
+TEST_F(GestureProviderTest, LongPressAndTapCancelledWhenScrollBegins) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX * 5,
+                            kFakeCoordY * 5);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX * 10,
+                            kFakeCoordY * 10);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  const base::TimeDelta long_press_timeout =
+      GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
+  RunTasksAndWait(long_press_timeout);
+
+  // No LONG_TAP as the LONG_PRESS timer is cancelled.
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
+}
+
+// Verify that LONG_TAP is triggered after LONG_PRESS followed by an UP.
+TEST_F(GestureProviderTest, GestureLongTap) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  const base::TimeDelta long_press_timeout =
+      GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
+  RunTasksAndWait(long_press_timeout);
+
+  EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(event_time + kOneSecond, MotionEvent::ACTION_UP);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_LONG_TAP, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+}
+
+TEST_F(GestureProviderTest, GestureLongPressDoesNotPreventScrolling) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  const base::TimeDelta long_press_timeout =
+      GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
+  RunTasksAndWait(long_press_timeout);
+
+  EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  event = ObtainMotionEvent(event_time + long_press_timeout,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 100,
+                            kFakeCoordY + 100);
+  gesture_provider_->OnTouchEvent(event);
+
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+
+  event = ObtainMotionEvent(event_time + long_press_timeout,
+                            MotionEvent::ACTION_UP);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
+}
+
+TEST_F(GestureProviderTest, NoGestureLongPressDuringDoubleTap) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  int motion_event_id = 0;
+
+  MockMotionEvent event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event_time += GetValidDoubleTapDelay();
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_TRUE(gesture_provider_->IsDoubleTapInProgress());
+
+  const base::TimeDelta long_press_timeout =
+      GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
+  RunTasksAndWait(long_press_timeout);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_PRESS));
+
+  event = ObtainMotionEvent(event_time + long_press_timeout,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 20,
+                            kFakeCoordY + 20);
+  event.set_id(++motion_event_id);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_TRUE(gesture_provider_->IsDoubleTapInProgress());
+
+  event = ObtainMotionEvent(event_time + long_press_timeout + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY + 1);
+  event.set_id(++motion_event_id);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_FALSE(gesture_provider_->IsDoubleTapInProgress());
+}
+
+// Verify that the touch slop region is removed from the first scroll delta to
+// avoid a jump when starting to scroll.
+TEST_F(GestureProviderTest, TouchSlopRemovedFromScroll) {
+  const float touch_slop = GetTouchSlop();
+  const float scroll_delta = 5;
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + touch_slop + scroll_delta);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+  GestureEventData gesture = GetMostRecentGestureEvent();
+  EXPECT_EQ(0, gesture.details.scroll_x());
+  EXPECT_EQ(scroll_delta, gesture.details.scroll_y());
+  EXPECT_EQ(1, gesture.details.touch_points());
+}
+
+// Verify that movement within the touch slop region does not generate a scroll,
+// and that the slop region is correct even when using fractional coordinates.
+TEST_F(GestureProviderTest, NoScrollWithinTouchSlop) {
+  const float touch_slop = GetTouchSlop();
+  const float scale_factor = 2.5f;
+  const int touch_slop_pixels = static_cast<int>(scale_factor * touch_slop);
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + touch_slop_pixels / scale_factor,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + touch_slop_pixels / scale_factor);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX - touch_slop_pixels / scale_factor,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY - touch_slop_pixels / scale_factor);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+
+  event =
+      ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                        MotionEvent::ACTION_MOVE,
+                        kFakeCoordX,
+                        kFakeCoordY + (touch_slop_pixels + 1.f) / scale_factor);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+}
+
+TEST_F(GestureProviderTest, NoDoubleTapWhenTooRapid) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  // If the second tap follows the first in too short a time span, no double-tap
+  // will occur.
+  event_time += (GetDoubleTapMinTime() / 2);
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+}
+
+TEST_F(GestureProviderTest, NoDoubleTapWhenExplicitlyDisabled) {
+  // Ensure that double-tap gestures can be disabled.
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MockMotionEvent event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+
+  event_time += GetValidDoubleTapDelay();
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+
+  // Ensure that double-tap gestures can be interrupted.
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+  event_time = base::TimeTicks::Now();
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(5U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+
+  // Ensure that double-tap gestures can be resumed.
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+  event_time += GetValidDoubleTapDelay();
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+
+  event_time += GetValidDoubleTapDelay();
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_DOUBLE_TAP, GetMostRecentGestureEventType());
+}
+
+TEST_F(GestureProviderTest, NoDelayedTapWhenDoubleTapSupportToggled) {
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MockMotionEvent event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  EXPECT_EQ(2U, GetReceivedGestureCount());
+
+  // Disabling double-tap during the tap timeout should flush the delayed tap.
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+  EXPECT_EQ(3U, GetReceivedGestureCount());
+
+  // No further timeout gestures should arrive.
+  const base::TimeDelta long_press_timeout =
+      GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
+  RunTasksAndWait(long_press_timeout);
+  EXPECT_EQ(3U, GetReceivedGestureCount());
+}
+
+TEST_F(GestureProviderTest, NoDoubleTapDragZoomWhenDisabledOnPlatform) {
+  const base::TimeTicks down_time_1 = TimeTicks::Now();
+  const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();
+
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+
+  event = ObtainMotionEvent(
+      down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+
+  // The move should become a scroll, as doubletap drag zoom is disabled.
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(down_time_2 + kOneMicrosecond * 2,
+            GetMostRecentGestureEvent().time);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END));
+}
+
+// Verify that double tap drag zoom feature is not invoked when the gesture
+// handler is told to disable double tap gesture detection.
+// The second tap sequence should be treated just as the first would be.
+TEST_F(GestureProviderTest, NoDoubleTapDragZoomWhenDisabledOnPage) {
+  const base::TimeTicks down_time_1 = TimeTicks::Now();
+  const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();
+
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+
+  event = ObtainMotionEvent(
+      down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+
+  // The move should become a scroll, as double tap drag zoom is disabled.
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_SCROLL_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END));
+}
+
+// Verify that updating double tap support during a double tap drag zoom
+// disables double tap detection after the gesture has ended.
+TEST_F(GestureProviderTest, FixedPageScaleDuringDoubleTapDragZoom) {
+  base::TimeTicks down_time_1 = TimeTicks::Now();
+  base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();
+
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(true);
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+  // Start a double-tap drag gesture.
+  MockMotionEvent event =
+      ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+  event = ObtainMotionEvent(
+      down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  // Simulate setting a fixed page scale (or a mobile viewport);
+  // this should not disrupt the current double-tap gesture.
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
+
+  // Double tap zoom updates should continue.
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale());
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_END));
+  EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  // The double-tap gesture has finished, but the page scale is fixed.
+  // The same event sequence should not generate any double tap getsures.
+  gestures_.clear();
+  down_time_1 += kOneMicrosecond * 40;
+  down_time_2 += kOneMicrosecond * 40;
+
+  // Start a double-tap drag gesture.
+  event = ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(down_time_1 + kOneMicrosecond,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  gesture_provider_->OnTouchEvent(event);
+  event = ObtainMotionEvent(
+      down_time_2, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 100);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+
+  // Double tap zoom updates should not be sent.
+  // Instead, the second tap drag becomes a scroll gesture sequence.
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_UP,
+                            kFakeCoordX,
+                            kFakeCoordY + 200);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_END));
+}
+
+// Verify that pinch zoom sends the proper event sequence.
+TEST_F(GestureProviderTest, PinchZoom) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  const float touch_slop = GetTouchSlop();
+  const float raw_offset_x = 3.2f;
+  const float raw_offset_y = 4.3f;
+  int motion_event_id = 0;
+
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+  gesture_provider_->SetMultiTouchZoomSupportEnabled(true);
+
+  int secondary_coord_x = kFakeCoordX + 20 * touch_slop;
+  int secondary_coord_y = kFakeCoordY + 20 * touch_slop;
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.set_id(++motion_event_id);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(kFakeCoordX, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(kFakeCoordY, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(kFakeCoordX + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(kFakeCoordY + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  // Toggling double-tap support should not take effect until the next sequence.
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(true);
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x,
+                            secondary_coord_y);
+  event.set_id(++motion_event_id);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  secondary_coord_x += 5 * touch_slop;
+  secondary_coord_y += 5 * touch_slop;
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x,
+                            secondary_coord_y);
+  event.set_id(++motion_event_id);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+
+  // Toggling double-tap support should not take effect until the next sequence.
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));
+
+  EXPECT_EQ((kFakeCoordX + secondary_coord_x) / 2, GetReceivedGesture(3).x);
+  EXPECT_EQ((kFakeCoordY + secondary_coord_y) / 2, GetReceivedGesture(3).y);
+  EXPECT_EQ((kFakeCoordX + secondary_coord_x) / 2 + raw_offset_x,
+            GetReceivedGesture(3).raw_x);
+  EXPECT_EQ((kFakeCoordY + secondary_coord_y) / 2 + raw_offset_y,
+            GetReceivedGesture(3).raw_y);
+
+  EXPECT_EQ(
+      gfx::RectF(kFakeCoordX - kMockTouchRadius,
+                 kFakeCoordY - kMockTouchRadius,
+                 secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2,
+                 secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2),
+      GetMostRecentGestureEvent().details.bounding_box());
+
+  secondary_coord_x += 2 * touch_slop;
+  secondary_coord_y += 2 * touch_slop;
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x,
+                            secondary_coord_y);
+  event.set_id(++motion_event_id);
+
+  // Toggling double-tap support should not take effect until the next sequence.
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(true);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));
+  EXPECT_EQ(ET_GESTURE_PINCH_UPDATE, GetMostRecentGestureEventType());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_LT(1.f, GetMostRecentGestureEvent().details.scale());
+  EXPECT_EQ(
+      gfx::RectF(kFakeCoordX - kMockTouchRadius,
+                 kFakeCoordY - kMockTouchRadius,
+                 secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2,
+                 secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2),
+      GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_UP,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x,
+                            secondary_coord_y);
+  event.set_id(++motion_event_id);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(ET_GESTURE_PINCH_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SCROLL_END));
+  EXPECT_EQ(
+      gfx::RectF(kFakeCoordX - kMockTouchRadius,
+                 kFakeCoordY - kMockTouchRadius,
+                 secondary_coord_x - kFakeCoordX + kMockTouchRadius * 2,
+                 secondary_coord_y - kFakeCoordY + kMockTouchRadius * 2),
+      GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(gfx::RectF(kFakeCoordX - kMockTouchRadius,
+                       kFakeCoordY - kMockTouchRadius,
+                       kMockTouchRadius * 2,
+                       kMockTouchRadius * 2),
+            GetMostRecentGestureEvent().details.bounding_box());
+}
+
+// Verify that no accidental pinching occurs if the touch size is large relative
+// to the min scaling span when the touch major value is used in scaling.
+TEST_F(GestureProviderTest, NoPinchZoomWithFatFinger) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  const float kFatFingerSize = GetMinScalingSpan() * 3.f;
+
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+  gesture_provider_->SetMultiTouchZoomSupportEnabled(true);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneSecond,
+                            MotionEvent::ACTION_MOVE);
+  event.SetTouchMajor(0.1f);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneSecond * 2,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 1.f,
+                            kFakeCoordY);
+  event.SetTouchMajor(1.f);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneSecond * 3,
+                            MotionEvent::ACTION_MOVE);
+  event.SetTouchMajor(kFatFingerSize * 3.5f);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneSecond * 4,
+                            MotionEvent::ACTION_MOVE);
+  event.SetTouchMajor(kFatFingerSize * 5.f);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(event_time + kOneSecond * 4,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 50.f,
+                            kFakeCoordY - 25.f);
+  event.SetTouchMajor(kFatFingerSize * 10.f);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+
+  event = ObtainMotionEvent(event_time + kOneSecond * 4,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX + 100.f,
+                            kFakeCoordY - 50.f);
+  event.SetTouchMajor(kFatFingerSize * 5.f);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+}
+
+// Verify that multi-finger swipe sends the proper event sequence.
+TEST_F(GestureProviderTest, MultiFingerSwipe) {
+  EnableSwipe();
+  gesture_provider_->SetMultiTouchZoomSupportEnabled(false);
+  const float min_swipe_velocity = GetMinSwipeVelocity();
+
+  // One finger - swipe right
+  OneFingerSwipe(2 * min_swipe_velocity, 0);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // One finger - swipe left
+  OneFingerSwipe(-2 * min_swipe_velocity, 0);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_left());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // One finger - swipe down
+  OneFingerSwipe(0, 2 * min_swipe_velocity);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // One finger - swipe up
+  OneFingerSwipe(0, -2 * min_swipe_velocity);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_up());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // Two fingers
+  // Swipe right.
+  TwoFingerSwipe(min_swipe_velocity * 2, 0, min_swipe_velocity * 2, 0);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // Swipe left.
+  TwoFingerSwipe(-min_swipe_velocity * 2, 0, -min_swipe_velocity * 2, 0);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_left());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // No swipe with different touch directions.
+  TwoFingerSwipe(min_swipe_velocity * 2, 0, -min_swipe_velocity * 2, 0);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  ResetGestureDetection();
+
+  // No swipe without a dominant direction.
+  TwoFingerSwipe(min_swipe_velocity * 2,
+                 min_swipe_velocity * 2,
+                 min_swipe_velocity * 2,
+                 min_swipe_velocity * 2);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  ResetGestureDetection();
+
+  // Swipe down with non-zero velocities on both axes and dominant direction.
+  TwoFingerSwipe(-min_swipe_velocity,
+                 min_swipe_velocity * 4,
+                 -min_swipe_velocity,
+                 min_swipe_velocity * 4);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down());
+  EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_left());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // Swipe up with non-zero velocities on both axes.
+  TwoFingerSwipe(min_swipe_velocity,
+                 -min_swipe_velocity * 4,
+                 min_swipe_velocity,
+                 -min_swipe_velocity * 4);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_up());
+  EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_right());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // No swipe without sufficient velocity.
+  TwoFingerSwipe(min_swipe_velocity / 2, 0, 0, 0);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  ResetGestureDetection();
+
+  // Swipe up with one small and one medium velocity in slightly different but
+  // not opposing directions.
+  TwoFingerSwipe(min_swipe_velocity / 2,
+                 min_swipe_velocity / 2,
+                 0,
+                 min_swipe_velocity * 2);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_down());
+  EXPECT_FALSE(GetMostRecentGestureEvent().details.swipe_right());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // No swipe in orthogonal directions.
+  TwoFingerSwipe(min_swipe_velocity * 2, 0, 0, min_swipe_velocity * 7);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  ResetGestureDetection();
+
+  // Three finger swipe in same directions.
+  ThreeFingerSwipe(min_swipe_velocity * 2,
+                   0,
+                   min_swipe_velocity * 3,
+                   0,
+                   min_swipe_velocity * 4,
+                   0);
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SWIPE));
+  EXPECT_TRUE(GetMostRecentGestureEvent().details.swipe_right());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
+  ResetGestureDetection();
+
+  // No three finger swipe in different directions.
+  ThreeFingerSwipe(min_swipe_velocity * 2,
+                   0,
+                   0,
+                   min_swipe_velocity * 3,
+                   min_swipe_velocity * 4,
+                   0);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_SWIPE));
+}
+
+// Verify that the timer of LONG_PRESS will be cancelled when scrolling begins
+// so LONG_PRESS and LONG_TAP won't be triggered.
+TEST_F(GestureProviderTest, GesturesCancelledAfterLongPressCausesLostFocus) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  const base::TimeDelta long_press_timeout =
+      GetLongPressTimeout() + GetShowPressTimeout() + kOneMicrosecond;
+  RunTasksAndWait(long_press_timeout);
+  EXPECT_EQ(ET_GESTURE_LONG_PRESS, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  EXPECT_TRUE(CancelActiveTouchSequence());
+  EXPECT_FALSE(HasDownEvent());
+
+  event = ObtainMotionEvent(event_time + long_press_timeout,
+                            MotionEvent::ACTION_UP);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_LONG_TAP));
+}
+
+// Verify that inserting a touch cancel event will trigger proper touch and
+// gesture sequence cancellation.
+TEST_F(GestureProviderTest, CancelActiveTouchSequence) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  int motion_event_id = 0;
+
+  EXPECT_FALSE(CancelActiveTouchSequence());
+  EXPECT_EQ(0U, GetReceivedGestureCount());
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.set_id(++motion_event_id);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  ASSERT_TRUE(CancelActiveTouchSequence());
+  EXPECT_FALSE(HasDownEvent());
+
+  // Subsequent MotionEvent's are dropped until ACTION_DOWN.
+  event = ObtainMotionEvent(event_time + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE);
+  EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_UP);
+  EXPECT_FALSE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+}
+
+TEST_F(GestureProviderTest, DoubleTapDragZoomCancelledOnSecondaryPointerDown) {
+  const base::TimeTicks down_time_1 = TimeTicks::Now();
+  const base::TimeTicks down_time_2 = down_time_1 + GetValidDoubleTapDelay();
+
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(down_time_1, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event =
+      ObtainMotionEvent(down_time_1 + kOneMicrosecond, MotionEvent::ACTION_UP);
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(ET_GESTURE_TAP_UNCONFIRMED, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(down_time_2, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY - 30);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_EQ(ET_GESTURE_PINCH_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 2,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY - 30,
+                            kFakeCoordX + 50,
+                            kFakeCoordY + 50);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_PINCH_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+
+  const size_t gesture_count = GetReceivedGestureCount();
+  event = ObtainMotionEvent(down_time_2 + kOneMicrosecond * 3,
+                            MotionEvent::ACTION_POINTER_UP,
+                            kFakeCoordX,
+                            kFakeCoordY - 30,
+                            kFakeCoordX + 50,
+                            kFakeCoordY + 50);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(gesture_count, GetReceivedGestureCount());
+
+  event = ObtainMotionEvent(down_time_2 + kOneSecond,
+                            MotionEvent::ACTION_UP);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(gesture_count + 1, GetReceivedGestureCount());
+  EXPECT_EQ(ET_GESTURE_SCROLL_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+}
+
+// Verify that gesture begin and gesture end events are dispatched correctly.
+TEST_F(GestureProviderTest, GestureBeginAndEnd) {
+  EnableBeginEndTypes();
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  const float raw_offset_x = 7.5f;
+  const float raw_offset_y = 5.7f;
+
+  EXPECT_EQ(0U, GetReceivedGestureCount());
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 1, 1);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetReceivedGesture(0).type());
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(2U, GetReceivedGestureCount());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(1 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(1 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+  EXPECT_EQ(gfx::RectF(1 - kMockTouchRadius,
+                       1 - kMockTouchRadius,
+                       kMockTouchRadius * 2,
+                       kMockTouchRadius * 2),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(3U, GetReceivedGestureCount());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(2, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(2 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(2 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2, 3, 3);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(4U, GetReceivedGestureCount());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(3, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(3 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(3 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_UP, 1, 1, 2, 2, 3, 3);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(5U, GetReceivedGestureCount());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(1 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(1 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_DOWN, 2, 2, 3, 3, 4, 4);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(6U, GetReceivedGestureCount());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(4, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(4, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(4 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(4 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_UP, 2, 2, 3, 3, 4, 4);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(7U, GetReceivedGestureCount());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(2, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(2 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(2 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+
+  event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_POINTER_UP, 3, 3, 4, 4);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(8U, GetReceivedGestureCount());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(3, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(3 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(3 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+
+
+  event = ObtainMotionEvent(event_time, MotionEvent::ACTION_UP, 4, 4);
+  event.SetRawOffset(raw_offset_x, raw_offset_y);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEventType());
+  EXPECT_EQ(9U, GetReceivedGestureCount());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(4, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(4, GetMostRecentGestureEvent().y);
+  EXPECT_EQ(4 + raw_offset_x, GetMostRecentGestureEvent().raw_x);
+  EXPECT_EQ(4 + raw_offset_y, GetMostRecentGestureEvent().raw_y);
+}
+
+// Verify that gesture begin and gesture end events are dispatched correctly
+// when an ACTION_CANCEL is received.
+TEST_F(GestureProviderTest, GestureBeginAndEndOnCancel) {
+  EnableBeginEndTypes();
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  EXPECT_EQ(0U, GetReceivedGestureCount());
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 1, 1);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetReceivedGesture(0).type());
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(2U, GetReceivedGestureCount());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(gfx::RectF(1 - kMockTouchRadius,
+                       1 - kMockTouchRadius,
+                       kMockTouchRadius * 2,
+                       kMockTouchRadius * 2),
+            GetMostRecentGestureEvent().details.bounding_box());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(3U, GetReceivedGestureCount());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(2, GetMostRecentGestureEvent().y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_DOWN, 1, 1, 2, 2, 3, 3);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_BEGIN, GetMostRecentGestureEventType());
+  EXPECT_EQ(4U, GetReceivedGestureCount());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(3, GetMostRecentGestureEvent().y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_CANCEL, 1, 1, 2, 2, 3, 3);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(5U, GetReceivedGestureCount());
+  EXPECT_EQ(3, GetReceivedGesture(4).details.touch_points());
+  EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(4).type());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_CANCEL, 1, 1, 3, 3);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(6U, GetReceivedGestureCount());
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEvent().type());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(1, GetMostRecentGestureEvent().y);
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_CANCEL, 3, 3);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(ET_GESTURE_END, GetMostRecentGestureEvent().type());
+  EXPECT_EQ(3, GetMostRecentGestureEvent().x);
+  EXPECT_EQ(3, GetMostRecentGestureEvent().y);
+}
+
+// Test a simple two finger tap
+TEST_F(GestureProviderTest, TwoFingerTap) {
+  // The time between ACTION_POINTER_DOWN and ACTION_POINTER_UP must be <= the
+  // two finger tap delay.
+  EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta());
+  const float scaled_touch_slop = GetTouchSlop();
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 0, 0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            0,
+                            scaled_touch_slop / 2);
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            0,
+                            0,
+                            kMaxTwoFingerTapSeparation / 2,
+                            0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event =
+      ObtainMotionEvent(event_time,
+                        MotionEvent::ACTION_MOVE,
+                        0,
+                        -scaled_touch_slop / 2,
+                        kMaxTwoFingerTapSeparation / 2 + scaled_touch_slop / 2,
+                        0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_UP,
+                            0,
+                            0,
+                            kMaxTwoFingerTapSeparation,
+                            0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
+  EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
+  EXPECT_EQ(ET_GESTURE_TWO_FINGER_TAP, GetReceivedGesture(2).type());
+  EXPECT_EQ(3U, GetReceivedGestureCount());
+
+  EXPECT_EQ(kMockTouchRadius * 2,
+            GetReceivedGesture(2).details.first_finger_width());
+  EXPECT_EQ(kMockTouchRadius * 2,
+            GetReceivedGesture(2).details.first_finger_height());
+}
+
+// Test preventing a two finger tap via finger movement.
+TEST_F(GestureProviderTest, TwoFingerTapCancelledByFingerMovement) {
+  EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta());
+  const float scaled_touch_slop = GetTouchSlop();
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX + scaled_touch_slop + 0.1,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_UP,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
+  EXPECT_EQ(ET_GESTURE_SCROLL_BEGIN, GetReceivedGesture(1).type());
+  EXPECT_EQ(2U, GetReceivedGestureCount());
+}
+
+// Test preventing a two finger tap by waiting too long before releasing the
+// secondary pointer.
+TEST_F(GestureProviderTest, TwoFingerTapCancelledByDelay) {
+  base::TimeDelta two_finger_tap_timeout = kOneSecond;
+  EnableTwoFingerTap(kMaxTwoFingerTapSeparation, two_finger_tap_timeout);
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY);
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX + kMaxTwoFingerTapSeparation / 2,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time + kOneSecond + kOneMicrosecond,
+                            MotionEvent::ACTION_POINTER_UP,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX + kMaxTwoFingerTapSeparation / 2,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+}
+
+// Test preventing a two finger tap by pressing the secondary pointer too far
+// from the first
+TEST_F(GestureProviderTest, TwoFingerTapCancelledByDistanceBetweenPointers) {
+  EnableTwoFingerTap(kMaxTwoFingerTapSeparation, base::TimeDelta());
+  base::TimeTicks event_time = base::TimeTicks::Now();
+
+  MockMotionEvent event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_DOWN, kFakeCoordX, kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX + kMaxTwoFingerTapSeparation,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_UP,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            kFakeCoordX + kMaxTwoFingerTapSeparation,
+                            kFakeCoordY);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetReceivedGesture(0).type());
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+}
+
+// Verify that pinch zoom only sends updates which exceed the
+// min_pinch_update_span_delta.
+TEST_F(GestureProviderTest, PinchZoomWithThreshold) {
+  const float kMinPinchUpdateDistance = 5;
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  const float touch_slop = GetTouchSlop();
+
+  SetMinPinchUpdateSpanDelta(kMinPinchUpdateDistance);
+  gesture_provider_->SetDoubleTapSupportForPageEnabled(false);
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+  gesture_provider_->SetMultiTouchZoomSupportEnabled(true);
+
+  int secondary_coord_x = kFakeCoordX + 20 * touch_slop;
+  int secondary_coord_y = kFakeCoordY + 20 * touch_slop;
+
+  // First finger down.
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  // Second finger down.
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_POINTER_DOWN,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x,
+                            secondary_coord_y);
+
+  gesture_provider_->OnTouchEvent(event);
+  EXPECT_EQ(1U, GetReceivedGestureCount());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+
+  // Move second finger.
+  secondary_coord_x += 5 * touch_slop;
+  secondary_coord_y += 5 * touch_slop;
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x,
+                            secondary_coord_y);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_BEGIN));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_BEGIN));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_SCROLL_UPDATE));
+
+  // Small move, shouldn't trigger pinch.
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x + kMinPinchUpdateDistance,
+                            secondary_coord_y);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_FALSE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+
+  // Small move, but combined with the previous move, should trigger pinch. We
+  // need to overshoot kMinPinchUpdateDistance by a fair bit, as the span
+  // calculation factors in touch radius.
+  const float kOvershootMinPinchUpdateDistance = 3;
+  event = ObtainMotionEvent(event_time,
+                            MotionEvent::ACTION_MOVE,
+                            kFakeCoordX,
+                            kFakeCoordY,
+                            secondary_coord_x + kMinPinchUpdateDistance +
+                                kOvershootMinPinchUpdateDistance,
+                            secondary_coord_y);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_TRUE(HasReceivedGesture(ET_GESTURE_PINCH_UPDATE));
+  EXPECT_EQ(2, GetMostRecentGestureEvent().details.touch_points());
+}
+
+// Verify that the min gesture bound setting is honored.
+TEST_F(GestureProviderTest, MinGestureBoundsLength) {
+  const float kMinGestureBoundsLength = 10.f * kMockTouchRadius;
+  SetMinMaxGestureBoundsLength(kMinGestureBoundsLength, 0.f);
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(kMinGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(kMinGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+  event =
+      ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+  EXPECT_EQ(kMinGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(kMinGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().height());
+}
+
+TEST_F(GestureProviderTest, MaxGestureBoundsLength) {
+  const float kMaxGestureBoundsLength = kMockTouchRadius / 10.f;
+  SetMinMaxGestureBoundsLength(0.f, kMaxGestureBoundsLength);
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+  event =
+      ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+  EXPECT_EQ(kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().height());
+}
+
+TEST_F(GestureProviderTest, ZeroRadiusBoundingBox) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 10, 20);
+  event.SetTouchMajor(0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(gfx::RectF(10, 20, 0, 0),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_POINTER_DOWN, 10, 20, 110, 120);
+  event.SetTouchMajor(0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  event = ObtainMotionEvent(
+      event_time, MotionEvent::ACTION_MOVE, 10, 20, 110, 150);
+  event.SetTouchMajor(0);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(gfx::RectF(10, 20, 100, 130),
+            GetMostRecentGestureEvent().details.bounding_box());
+}
+
+// Verify that the min/max gesture bound settings are not applied to stylus
+// or mouse-derived MotionEvents.
+TEST_F(GestureProviderTest, NoMinOrMaxGestureBoundsLengthWithStylusOrMouse) {
+  const float kMinGestureBoundsLength = 5.f * kMockTouchRadius;
+  const float kMaxGestureBoundsLength = 10.f * kMockTouchRadius;
+  SetMinMaxGestureBoundsLength(kMinGestureBoundsLength,
+                               kMaxGestureBoundsLength);
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.SetTouchMajor(0);
+  event.SetToolType(0, MotionEvent::TOOL_TYPE_MOUSE);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(MotionEvent::TOOL_TYPE_MOUSE,
+            GetMostRecentGestureEvent().primary_tool_type);
+  EXPECT_EQ(0.f, GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(0.f, GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+  event =
+      ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+  event.SetTouchMajor(1);
+  event.SetToolType(0, MotionEvent::TOOL_TYPE_STYLUS);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+  EXPECT_EQ(MotionEvent::TOOL_TYPE_STYLUS,
+            GetMostRecentGestureEvent().primary_tool_type);
+  EXPECT_EQ(0, GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(0, GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+  event = ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+  event.SetTouchMajor(2.f * kMaxGestureBoundsLength);
+  event.SetToolType(0, MotionEvent::TOOL_TYPE_MOUSE);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(MotionEvent::TOOL_TYPE_MOUSE,
+            GetMostRecentGestureEvent().primary_tool_type);
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().height());
+
+  event =
+      ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+  event.SetTouchMajor(2.f * kMaxGestureBoundsLength);
+  event.SetToolType(0, MotionEvent::TOOL_TYPE_ERASER);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+  EXPECT_EQ(MotionEvent::TOOL_TYPE_ERASER,
+            GetMostRecentGestureEvent().primary_tool_type);
+  EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().width());
+  EXPECT_EQ(2.f * kMaxGestureBoundsLength,
+            GetMostRecentGestureEvent().details.bounding_box_f().height());
+}
+
+// Test the bounding box for show press and tap gestures.
+TEST_F(GestureProviderTest, BoundingBoxForShowPressAndTapGesture) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+  base::TimeDelta showpress_timeout = kOneMicrosecond;
+  base::TimeDelta longpress_timeout = kOneSecond;
+  SetShowPressAndLongPressTimeout(showpress_timeout, longpress_timeout);
+
+  MockMotionEvent event =
+      ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN, 10, 10);
+  event.SetTouchMajor(10);
+
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(gfx::RectF(5, 5, 10, 10),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event = ObtainMotionEvent(
+      event_time + kOneMicrosecond, MotionEvent::ACTION_MOVE, 11, 9);
+  event.SetTouchMajor(20);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  event = ObtainMotionEvent(
+      event_time + kOneMicrosecond, MotionEvent::ACTION_MOVE, 8, 11);
+  event.SetTouchMajor(10);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  RunTasksAndWait(showpress_timeout + kOneMicrosecond);
+  EXPECT_EQ(ET_GESTURE_SHOW_PRESS, GetMostRecentGestureEventType());
+  EXPECT_EQ(gfx::RectF(0, 0, 20, 20),
+            GetMostRecentGestureEvent().details.bounding_box());
+
+  event =
+      ObtainMotionEvent(event_time + kOneMicrosecond, MotionEvent::ACTION_UP);
+  event.SetTouchMajor(30);
+  EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+  EXPECT_EQ(ET_GESTURE_TAP, GetMostRecentGestureEventType());
+
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.tap_count());
+  EXPECT_EQ(1, GetMostRecentGestureEvent().details.touch_points());
+  EXPECT_EQ(gfx::RectF(0, 0, 20, 20),
+            GetMostRecentGestureEvent().details.bounding_box());
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/gesture_touch_uma_histogram.cc b/ui/events/gesture_detection/gesture_touch_uma_histogram.cc
new file mode 100644
index 0000000..b8275aa
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_touch_uma_histogram.cc
@@ -0,0 +1,144 @@
+// Copyright 2014 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 "ui/events/gesture_detection/gesture_touch_uma_histogram.h"
+
+#include "base/metrics/histogram.h"
+
+namespace ui {
+
+GestureTouchUMAHistogram::GestureTouchUMAHistogram()
+    : max_distance_from_start_squared_(0), is_single_finger_(true) {
+}
+
+GestureTouchUMAHistogram::~GestureTouchUMAHistogram() {
+}
+
+void GestureTouchUMAHistogram::RecordGestureEvent(
+    const GestureEventData& gesture) {
+  UMA_HISTOGRAM_ENUMERATION(
+      "Event.GestureCreated", UMAEventTypeFromEvent(gesture), UMA_ET_COUNT);
+}
+
+void GestureTouchUMAHistogram::RecordTouchEvent(const MotionEvent& event) {
+  if (event.GetAction() == MotionEvent::ACTION_DOWN) {
+    start_time_ = event.GetEventTime();
+    start_touch_position_ = gfx::Point(event.GetX(), event.GetY());
+    is_single_finger_ = true;
+    max_distance_from_start_squared_ = 0;
+  } else if (event.GetAction() == MotionEvent::ACTION_MOVE &&
+             is_single_finger_) {
+    float cur_dist = (start_touch_position_ -
+                      gfx::Point(event.GetX(), event.GetY())).LengthSquared();
+    if (cur_dist > max_distance_from_start_squared_)
+      max_distance_from_start_squared_ = cur_dist;
+  } else {
+    if (event.GetAction() == MotionEvent::ACTION_UP && is_single_finger_) {
+      UMA_HISTOGRAM_CUSTOM_COUNTS(
+          "Event.TouchMaxDistance",
+          static_cast<int>(sqrt(max_distance_from_start_squared_)),
+          0,
+          1500,
+          50);
+
+      base::TimeDelta duration = event.GetEventTime() - start_time_;
+      UMA_HISTOGRAM_TIMES("Event.TouchDuration", duration);
+    }
+    is_single_finger_ = false;
+  }
+}
+
+UMAEventType GestureTouchUMAHistogram::UMAEventTypeFromEvent(
+    const GestureEventData& gesture) {
+  switch (gesture.type()) {
+    case ET_TOUCH_RELEASED:
+      return UMA_ET_TOUCH_RELEASED;
+    case ET_TOUCH_PRESSED:
+      return UMA_ET_TOUCH_PRESSED;
+    case ET_TOUCH_MOVED:
+      return UMA_ET_TOUCH_MOVED;
+    case ET_TOUCH_CANCELLED:
+      return UMA_ET_TOUCH_CANCELLED;
+    case ET_GESTURE_SCROLL_BEGIN:
+      return UMA_ET_GESTURE_SCROLL_BEGIN;
+    case ET_GESTURE_SCROLL_END:
+      return UMA_ET_GESTURE_SCROLL_END;
+    case ET_GESTURE_SCROLL_UPDATE: {
+      int touch_points = gesture.details.touch_points();
+      if (touch_points == 1)
+        return UMA_ET_GESTURE_SCROLL_UPDATE;
+      else if (touch_points == 2)
+        return UMA_ET_GESTURE_SCROLL_UPDATE_2;
+      else if (touch_points == 3)
+        return UMA_ET_GESTURE_SCROLL_UPDATE_3;
+      return UMA_ET_GESTURE_SCROLL_UPDATE_4P;
+    }
+    case ET_GESTURE_TAP: {
+      int tap_count = gesture.details.tap_count();
+      if (tap_count == 1)
+        return UMA_ET_GESTURE_TAP;
+      if (tap_count == 2)
+        return UMA_ET_GESTURE_DOUBLE_TAP;
+      if (tap_count == 3)
+        return UMA_ET_GESTURE_TRIPLE_TAP;
+      NOTREACHED() << "Received tap with tapcount " << tap_count;
+      return UMA_ET_UNKNOWN;
+    }
+    case ET_GESTURE_TAP_DOWN:
+      return UMA_ET_GESTURE_TAP_DOWN;
+    case ET_GESTURE_BEGIN:
+      return UMA_ET_GESTURE_BEGIN;
+    case ET_GESTURE_END:
+      return UMA_ET_GESTURE_END;
+    case ET_GESTURE_TWO_FINGER_TAP:
+      return UMA_ET_GESTURE_TWO_FINGER_TAP;
+    case ET_GESTURE_PINCH_BEGIN:
+      return UMA_ET_GESTURE_PINCH_BEGIN;
+    case ET_GESTURE_PINCH_END:
+      return UMA_ET_GESTURE_PINCH_END;
+    case ET_GESTURE_PINCH_UPDATE: {
+      int touch_points = gesture.details.touch_points();
+      if (touch_points >= 4)
+        return UMA_ET_GESTURE_PINCH_UPDATE_4P;
+      else if (touch_points == 3)
+        return UMA_ET_GESTURE_PINCH_UPDATE_3;
+      return UMA_ET_GESTURE_PINCH_UPDATE;
+    }
+    case ET_GESTURE_LONG_PRESS:
+      return UMA_ET_GESTURE_LONG_PRESS;
+    case ET_GESTURE_LONG_TAP:
+      return UMA_ET_GESTURE_LONG_TAP;
+    case ET_GESTURE_SWIPE: {
+      int touch_points = gesture.details.touch_points();
+      if (touch_points == 1)
+        return UMA_ET_GESTURE_SWIPE_1;
+      else if (touch_points == 2)
+        return UMA_ET_GESTURE_SWIPE_2;
+      else if (touch_points == 3)
+        return UMA_ET_GESTURE_SWIPE_3;
+      return UMA_ET_GESTURE_SWIPE_4P;
+    }
+    case ET_GESTURE_WIN8_EDGE_SWIPE:
+      return UMA_ET_GESTURE_WIN8_EDGE_SWIPE;
+    case ET_GESTURE_TAP_CANCEL:
+      return UMA_ET_GESTURE_TAP_CANCEL;
+    case ET_GESTURE_SHOW_PRESS:
+      return UMA_ET_GESTURE_SHOW_PRESS;
+    case ET_SCROLL:
+      return UMA_ET_SCROLL;
+    case ET_SCROLL_FLING_START:
+      return UMA_ET_SCROLL_FLING_START;
+    case ET_SCROLL_FLING_CANCEL:
+      return UMA_ET_SCROLL_FLING_CANCEL;
+    case ET_GESTURE_TAP_UNCONFIRMED:
+      return UMA_ET_GESTURE_TAP_UNCONFIRMED;
+    case ET_GESTURE_DOUBLE_TAP:
+      return UMA_ET_GESTURE_DOUBLE_TAP;
+    default:
+      NOTREACHED();
+      return UMA_ET_UNKNOWN;
+  }
+}
+
+}  //  namespace ui
diff --git a/ui/events/gesture_detection/gesture_touch_uma_histogram.h b/ui/events/gesture_detection/gesture_touch_uma_histogram.h
new file mode 100644
index 0000000..824a562
--- /dev/null
+++ b/ui/events/gesture_detection/gesture_touch_uma_histogram.h
@@ -0,0 +1,86 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_TOUCH_UMA_HISTOGRAM_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_TOUCH_UMA_HISTOGRAM_H_
+
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+
+enum UMAEventType {
+  // WARNING: Do not change the numerical values of any of these types.
+  // Do not remove deprecated types - just comment them as deprecated.
+  UMA_ET_UNKNOWN = 0,
+  UMA_ET_TOUCH_RELEASED = 1,
+  UMA_ET_TOUCH_PRESSED = 2,
+  UMA_ET_TOUCH_MOVED = 3,
+  UMA_ET_TOUCH_STATIONARY = 4,  // Deprecated. Do not remove.
+  UMA_ET_TOUCH_CANCELLED = 5,
+  UMA_ET_GESTURE_SCROLL_BEGIN = 6,
+  UMA_ET_GESTURE_SCROLL_END = 7,
+  UMA_ET_GESTURE_SCROLL_UPDATE = 8,
+  UMA_ET_GESTURE_TAP = 9,
+  UMA_ET_GESTURE_TAP_DOWN = 10,
+  UMA_ET_GESTURE_BEGIN = 11,
+  UMA_ET_GESTURE_END = 12,
+  UMA_ET_GESTURE_DOUBLE_TAP = 13,
+  UMA_ET_GESTURE_TRIPLE_TAP = 14,
+  UMA_ET_GESTURE_TWO_FINGER_TAP = 15,
+  UMA_ET_GESTURE_PINCH_BEGIN = 16,
+  UMA_ET_GESTURE_PINCH_END = 17,
+  UMA_ET_GESTURE_PINCH_UPDATE = 18,
+  UMA_ET_GESTURE_LONG_PRESS = 19,
+  UMA_ET_GESTURE_SWIPE_2 = 20,  // Swipe with 2 fingers
+  UMA_ET_SCROLL = 21,
+  UMA_ET_SCROLL_FLING_START = 22,
+  UMA_ET_SCROLL_FLING_CANCEL = 23,
+  UMA_ET_GESTURE_SWIPE_3 = 24,   // Swipe with 3 fingers
+  UMA_ET_GESTURE_SWIPE_4P = 25,  // Swipe with 4+ fingers
+  UMA_ET_GESTURE_SCROLL_UPDATE_2 = 26,
+  UMA_ET_GESTURE_SCROLL_UPDATE_3 = 27,
+  UMA_ET_GESTURE_SCROLL_UPDATE_4P = 28,
+  UMA_ET_GESTURE_PINCH_UPDATE_3 = 29,
+  UMA_ET_GESTURE_PINCH_UPDATE_4P = 30,
+  UMA_ET_GESTURE_LONG_TAP = 31,
+  UMA_ET_GESTURE_SHOW_PRESS = 32,
+  UMA_ET_GESTURE_TAP_CANCEL = 33,
+  UMA_ET_GESTURE_WIN8_EDGE_SWIPE = 34,
+  UMA_ET_GESTURE_SWIPE_1 = 35,  // Swipe with 1 finger
+  UMA_ET_GESTURE_TAP_UNCONFIRMED = 36,
+  // NOTE: Add new event types only immediately above this line. Make sure to
+  // update the UIEventType enum in tools/metrics/histograms/histograms.xml
+  // accordingly.
+  UMA_ET_COUNT
+};
+
+// Records some touch/gesture event specific details (e.g. what gestures are
+// targetted to which components etc.)
+class GESTURE_DETECTION_EXPORT GestureTouchUMAHistogram {
+ public:
+  GestureTouchUMAHistogram();
+  ~GestureTouchUMAHistogram();
+
+  static void RecordGestureEvent(const ui::GestureEventData& gesture);
+  void RecordTouchEvent(const ui::MotionEvent& event);
+
+ private:
+  static UMAEventType UMAEventTypeFromEvent(const GestureEventData& gesture);
+
+  // The first finger's press time.
+  base::TimeTicks start_time_;
+  // The first finger's press location.
+  gfx::Point start_touch_position_;
+  // The maximum distance the first touch point travelled from its starting
+  // location in pixels.
+  float max_distance_from_start_squared_;
+  bool is_single_finger_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_TOUCH_UMA_HISTOGRAM_H_
diff --git a/ui/events/gesture_detection/motion_event.cc b/ui/events/gesture_detection/motion_event.cc
new file mode 100644
index 0000000..71a6912
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event.cc
@@ -0,0 +1,93 @@
+// Copyright 2014 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 "ui/events/gesture_detection/motion_event.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+size_t MotionEvent::GetHistorySize() const {
+  return 0;
+}
+
+base::TimeTicks MotionEvent::GetHistoricalEventTime(
+    size_t historical_index) const {
+  NOTIMPLEMENTED();
+  return base::TimeTicks();
+}
+
+float MotionEvent::GetHistoricalTouchMajor(size_t pointer_index,
+                                           size_t historical_index) const {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float MotionEvent::GetHistoricalX(size_t pointer_index,
+                                  size_t historical_index) const {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+float MotionEvent::GetHistoricalY(size_t pointer_index,
+                                  size_t historical_index) const {
+  NOTIMPLEMENTED();
+  return 0.f;
+}
+
+int MotionEvent::FindPointerIndexOfId(int id) const {
+  const size_t pointer_count = GetPointerCount();
+  for (size_t i = 0; i < pointer_count; ++i) {
+    if (GetPointerId(i) == id)
+      return static_cast<int>(i);
+  }
+  return -1;
+}
+
+bool operator==(const MotionEvent& lhs, const MotionEvent& rhs) {
+  if (lhs.GetId() != rhs.GetId() || lhs.GetAction() != rhs.GetAction() ||
+      lhs.GetActionIndex() != rhs.GetActionIndex() ||
+      lhs.GetPointerCount() != rhs.GetPointerCount() ||
+      lhs.GetButtonState() != rhs.GetButtonState() ||
+      lhs.GetEventTime() != rhs.GetEventTime() ||
+      lhs.GetHistorySize() != rhs.GetHistorySize())
+    return false;
+
+  for (size_t i = 0; i < lhs.GetPointerCount(); ++i) {
+    int rhsi = rhs.FindPointerIndexOfId(lhs.GetPointerId(i));
+    if (rhsi == -1)
+      return false;
+
+    if (lhs.GetX(i) != rhs.GetX(rhsi) || lhs.GetY(i) != rhs.GetY(rhsi) ||
+        lhs.GetRawX(i) != rhs.GetRawX(rhsi) ||
+        lhs.GetRawY(i) != rhs.GetRawY(rhsi) ||
+        lhs.GetTouchMajor(i) != rhs.GetTouchMajor(rhsi) ||
+        lhs.GetTouchMinor(i) != rhs.GetTouchMinor(rhsi) ||
+        lhs.GetOrientation(i) != rhs.GetOrientation(rhsi) ||
+        lhs.GetPressure(i) != rhs.GetPressure(rhsi) ||
+        lhs.GetToolType(i) != rhs.GetToolType(rhsi))
+      return false;
+
+    for (size_t h = 0; h < lhs.GetHistorySize(); ++h) {
+      if (lhs.GetHistoricalX(i, h) != rhs.GetHistoricalX(rhsi, h) ||
+          lhs.GetHistoricalY(i, h) != rhs.GetHistoricalY(rhsi, h) ||
+          lhs.GetHistoricalTouchMajor(i, h) !=
+              rhs.GetHistoricalTouchMajor(rhsi, h))
+        return false;
+    }
+  }
+
+  for (size_t h = 0; h < lhs.GetHistorySize(); ++h) {
+    if (lhs.GetHistoricalEventTime(h) != rhs.GetHistoricalEventTime(h))
+      return false;
+  }
+
+  return true;
+}
+
+bool operator!=(const MotionEvent& lhs, const MotionEvent& rhs) {
+  return !(lhs == rhs);
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/motion_event.h b/ui/events/gesture_detection/motion_event.h
new file mode 100644
index 0000000..bb17ef1
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event.h
@@ -0,0 +1,110 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_
+#define UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+// Abstract class for a generic motion-related event, patterned after that
+// subset of Android's MotionEvent API used in gesture detection.
+class GESTURE_DETECTION_EXPORT MotionEvent {
+ public:
+  enum Action {
+    ACTION_DOWN,
+    ACTION_UP,
+    ACTION_MOVE,
+    ACTION_CANCEL,
+    ACTION_POINTER_DOWN,
+    ACTION_POINTER_UP,
+  };
+
+  enum ToolType {
+    TOOL_TYPE_UNKNOWN,
+    TOOL_TYPE_FINGER,
+    TOOL_TYPE_STYLUS,
+    TOOL_TYPE_MOUSE,
+    TOOL_TYPE_ERASER
+  };
+
+  enum ButtonType {
+    BUTTON_PRIMARY = 1 << 0,
+    BUTTON_SECONDARY = 1 << 1,
+    BUTTON_TERTIARY = 1 << 2,
+    BUTTON_BACK = 1 << 3,
+    BUTTON_FORWARD = 1 << 4,
+  };
+
+  // The implementer promises that |GetPointerId()| will never exceed
+  // MAX_POINTER_ID.
+  enum { MAX_POINTER_ID = 31, MAX_TOUCH_POINT_COUNT = 12 };
+
+  virtual ~MotionEvent() {}
+
+  virtual int GetId() const = 0;
+  virtual Action GetAction() const = 0;
+  // Only valid if |GetAction()| returns ACTION_POINTER_UP or
+  // ACTION_POINTER_DOWN.
+  virtual int GetActionIndex() const = 0;
+  virtual size_t GetPointerCount() const = 0;
+  virtual int GetPointerId(size_t pointer_index) const = 0;
+  virtual float GetX(size_t pointer_index) const = 0;
+  virtual float GetY(size_t pointer_index) const = 0;
+  virtual float GetRawX(size_t pointer_index) const = 0;
+  virtual float GetRawY(size_t pointer_index) const = 0;
+  virtual float GetTouchMajor(size_t pointer_index) const = 0;
+  virtual float GetTouchMinor(size_t pointer_index) const = 0;
+  virtual float GetOrientation(size_t pointer_index) const = 0;
+  virtual float GetPressure(size_t pointer_index) const = 0;
+  virtual ToolType GetToolType(size_t pointer_index) const = 0;
+  virtual int GetButtonState() const = 0;
+  virtual int GetFlags() const = 0;
+  virtual base::TimeTicks GetEventTime() const = 0;
+
+  // Optional historical data, default implementation provides an empty history.
+  virtual size_t GetHistorySize() const;
+  virtual base::TimeTicks GetHistoricalEventTime(size_t historical_index) const;
+  virtual float GetHistoricalTouchMajor(size_t pointer_index,
+                                        size_t historical_index) const;
+  virtual float GetHistoricalX(size_t pointer_index,
+                               size_t historical_index) const;
+  virtual float GetHistoricalY(size_t pointer_index,
+                               size_t historical_index) const;
+
+  virtual scoped_ptr<MotionEvent> Clone() const = 0;
+  virtual scoped_ptr<MotionEvent> Cancel() const = 0;
+
+  float GetX() const { return GetX(0); }
+  float GetY() const { return GetY(0); }
+  float GetRawX() const { return GetRawX(0); }
+  float GetRawY() const { return GetRawY(0); }
+  float GetRawOffsetX() const { return GetRawX() - GetX(); }
+  float GetRawOffsetY() const { return GetRawY() - GetY(); }
+
+  float GetTouchMajor() const { return GetTouchMajor(0); }
+  float GetTouchMinor() const { return GetTouchMinor(0); }
+
+  // Returns the orientation of the major axis clockwise from vertical, in
+  // radians. The return value lies in [-PI/2, PI/2].
+  float GetOrientation() const { return GetOrientation(0); }
+
+  float GetPressure() const { return GetPressure(0); }
+  ToolType GetToolType() const { return GetToolType(0); }
+
+  // O(N) search of pointers (use sparingly!). Returns -1 if |id| nonexistent.
+  int FindPointerIndexOfId(int id) const;
+};
+
+GESTURE_DETECTION_EXPORT bool operator==(const MotionEvent& lhs,
+                                         const MotionEvent& rhs);
+GESTURE_DETECTION_EXPORT bool operator!=(const MotionEvent& lhs,
+                                         const MotionEvent& rhs);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_
diff --git a/ui/events/gesture_detection/motion_event_buffer.cc b/ui/events/gesture_detection/motion_event_buffer.cc
new file mode 100644
index 0000000..e8e0eb1
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_buffer.cc
@@ -0,0 +1,464 @@
+// Copyright 2014 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 "ui/events/gesture_detection/motion_event_buffer.h"
+
+#include "base/debug/trace_event.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/gesture_detection/motion_event_generic.h"
+
+namespace ui {
+namespace {
+
+// Latency added during resampling. A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+const int kResampleLatencyMs = 5;
+
+// Minimum time difference between consecutive samples before attempting to
+// resample.
+const int kResampleMinDeltaMs = 2;
+
+// Maximum time to predict forward from the last known state, to avoid
+// predicting too far into the future.  This time is further bounded by 50% of
+// the last time delta.
+const int kResampleMaxPredictionMs = 8;
+
+typedef ScopedVector<MotionEvent> MotionEventVector;
+
+float Lerp(float a, float b, float alpha) {
+  return a + alpha * (b - a);
+}
+
+bool CanAddSample(const MotionEvent& event0, const MotionEvent& event1) {
+  DCHECK_EQ(event0.GetAction(), MotionEvent::ACTION_MOVE);
+  if (event1.GetAction() != MotionEvent::ACTION_MOVE)
+    return false;
+
+  const size_t pointer_count = event0.GetPointerCount();
+  if (pointer_count != event1.GetPointerCount())
+    return false;
+
+  for (size_t event0_i = 0; event0_i < pointer_count; ++event0_i) {
+    const int id = event0.GetPointerId(event0_i);
+    const int event1_i = event1.FindPointerIndexOfId(id);
+    if (event1_i == -1)
+      return false;
+    if (event0.GetToolType(event0_i) != event1.GetToolType(event1_i))
+      return false;
+  }
+
+  return true;
+}
+
+bool ShouldResampleTool(MotionEvent::ToolType tool) {
+  return tool == MotionEvent::TOOL_TYPE_UNKNOWN ||
+         tool == MotionEvent::TOOL_TYPE_FINGER;
+}
+
+size_t CountSamplesNoLaterThan(const MotionEventVector& batch,
+                               base::TimeTicks time) {
+  size_t count = 0;
+  while (count < batch.size() && batch[count]->GetEventTime() <= time)
+    ++count;
+  return count;
+}
+
+MotionEventVector ConsumeSamplesNoLaterThan(MotionEventVector* batch,
+                                            base::TimeTicks time) {
+  DCHECK(batch);
+  size_t count = CountSamplesNoLaterThan(*batch, time);
+  DCHECK_GE(batch->size(), count);
+  if (count == 0)
+    return MotionEventVector();
+
+  if (count == batch->size())
+    return batch->Pass();
+
+  // TODO(jdduke): Use a ScopedDeque to work around this mess.
+  MotionEventVector unconsumed_batch;
+  unconsumed_batch.insert(
+      unconsumed_batch.begin(), batch->begin() + count, batch->end());
+  batch->weak_erase(batch->begin() + count, batch->end());
+
+  unconsumed_batch.swap(*batch);
+  DCHECK_GE(unconsumed_batch.size(), 1U);
+  return unconsumed_batch.Pass();
+}
+
+PointerProperties PointerFromMotionEvent(const MotionEvent& event,
+                                         size_t pointer_index) {
+  PointerProperties result;
+  result.id = event.GetPointerId(pointer_index);
+  result.tool_type = event.GetToolType(pointer_index);
+  result.x = event.GetX(pointer_index);
+  result.y = event.GetY(pointer_index);
+  result.raw_x = event.GetRawX(pointer_index);
+  result.raw_y = event.GetRawY(pointer_index);
+  result.pressure = event.GetPressure(pointer_index);
+  result.touch_major = event.GetTouchMajor(pointer_index);
+  result.touch_minor = event.GetTouchMinor(pointer_index);
+  result.orientation = event.GetOrientation(pointer_index);
+  return result;
+}
+
+PointerProperties ResamplePointer(const MotionEvent& event0,
+                                  const MotionEvent& event1,
+                                  size_t event0_pointer_index,
+                                  size_t event1_pointer_index,
+                                  float alpha) {
+  DCHECK_EQ(event0.GetPointerId(event0_pointer_index),
+            event1.GetPointerId(event1_pointer_index));
+  // If the tool should not be resampled, use the latest event in the valid
+  // horizon (i.e., the event no later than the time interpolated by alpha).
+  if (!ShouldResampleTool(event0.GetToolType(event0_pointer_index))) {
+    if (alpha > 1)
+      return PointerFromMotionEvent(event1, event1_pointer_index);
+    else
+      return PointerFromMotionEvent(event0, event0_pointer_index);
+  }
+
+  PointerProperties p(PointerFromMotionEvent(event0, event0_pointer_index));
+  p.x = Lerp(p.x, event1.GetX(event1_pointer_index), alpha);
+  p.y = Lerp(p.y, event1.GetY(event1_pointer_index), alpha);
+  p.raw_x = Lerp(p.raw_x, event1.GetRawX(event1_pointer_index), alpha);
+  p.raw_y = Lerp(p.raw_y, event1.GetRawY(event1_pointer_index), alpha);
+  return p;
+}
+
+scoped_ptr<MotionEvent> ResampleMotionEvent(const MotionEvent& event0,
+                                            const MotionEvent& event1,
+                                            base::TimeTicks resample_time) {
+  DCHECK_EQ(MotionEvent::ACTION_MOVE, event0.GetAction());
+  DCHECK_EQ(event0.GetPointerCount(), event1.GetPointerCount());
+
+  const base::TimeTicks time0 = event0.GetEventTime();
+  const base::TimeTicks time1 = event1.GetEventTime();
+  DCHECK(time0 < time1);
+  DCHECK(time0 <= resample_time);
+
+  const float alpha = (resample_time - time0).InMillisecondsF() /
+                      (time1 - time0).InMillisecondsF();
+
+  scoped_ptr<MotionEventGeneric> event;
+  const size_t pointer_count = event0.GetPointerCount();
+  DCHECK_EQ(pointer_count, event1.GetPointerCount());
+  for (size_t event0_i = 0; event0_i < pointer_count; ++event0_i) {
+    int event1_i = event1.FindPointerIndexOfId(event0.GetPointerId(event0_i));
+    DCHECK_NE(event1_i, -1);
+    PointerProperties pointer = ResamplePointer(
+        event0, event1, event0_i, static_cast<size_t>(event1_i), alpha);
+
+    if (event0_i == 0) {
+      event.reset(new MotionEventGeneric(
+          MotionEvent::ACTION_MOVE, resample_time, pointer));
+    } else {
+      event->PushPointer(pointer);
+    }
+  }
+
+  DCHECK(event);
+  event->set_id(event0.GetId());
+  event->set_action_index(event0.GetActionIndex());
+  event->set_button_state(event0.GetButtonState());
+
+  return event.PassAs<MotionEvent>();
+}
+
+// MotionEvent implementation for storing multiple events, with the most
+// recent event used as the base event, and prior events used as the history.
+class CompoundMotionEvent : public ui::MotionEvent {
+ public:
+  explicit CompoundMotionEvent(MotionEventVector events)
+      : events_(events.Pass()) {
+    DCHECK_GE(events_.size(), 1U);
+  }
+  virtual ~CompoundMotionEvent() {}
+
+  virtual int GetId() const OVERRIDE { return latest().GetId(); }
+
+  virtual Action GetAction() const OVERRIDE { return latest().GetAction(); }
+
+  virtual int GetActionIndex() const OVERRIDE {
+    return latest().GetActionIndex();
+  }
+
+  virtual size_t GetPointerCount() const OVERRIDE {
+    return latest().GetPointerCount();
+  }
+
+  virtual int GetPointerId(size_t pointer_index) const OVERRIDE {
+    return latest().GetPointerId(pointer_index);
+  }
+
+  virtual float GetX(size_t pointer_index) const OVERRIDE {
+    return latest().GetX(pointer_index);
+  }
+
+  virtual float GetY(size_t pointer_index) const OVERRIDE {
+    return latest().GetY(pointer_index);
+  }
+
+  virtual float GetRawX(size_t pointer_index) const OVERRIDE {
+    return latest().GetRawX(pointer_index);
+  }
+
+  virtual float GetRawY(size_t pointer_index) const OVERRIDE {
+    return latest().GetRawY(pointer_index);
+  }
+
+  virtual float GetTouchMajor(size_t pointer_index) const OVERRIDE {
+    return latest().GetTouchMajor(pointer_index);
+  }
+
+  virtual float GetTouchMinor(size_t pointer_index) const OVERRIDE {
+    return latest().GetTouchMinor(pointer_index);
+  }
+
+  virtual float GetOrientation(size_t pointer_index) const OVERRIDE {
+    return latest().GetOrientation(pointer_index);
+  }
+
+  virtual float GetPressure(size_t pointer_index) const OVERRIDE {
+    return latest().GetPressure(pointer_index);
+  }
+
+  virtual ToolType GetToolType(size_t pointer_index) const OVERRIDE {
+    return latest().GetToolType(pointer_index);
+  }
+
+  virtual int GetButtonState() const OVERRIDE {
+    return latest().GetButtonState();
+  }
+
+  virtual int GetFlags() const OVERRIDE { return latest().GetFlags(); }
+
+  virtual base::TimeTicks GetEventTime() const OVERRIDE {
+    return latest().GetEventTime();
+  }
+
+  virtual size_t GetHistorySize() const OVERRIDE { return events_.size() - 1; }
+
+  virtual base::TimeTicks GetHistoricalEventTime(
+      size_t historical_index) const OVERRIDE {
+    DCHECK_LT(historical_index, GetHistorySize());
+    return events_[historical_index]->GetEventTime();
+  }
+
+  virtual float GetHistoricalTouchMajor(
+      size_t pointer_index,
+      size_t historical_index) const OVERRIDE {
+    DCHECK_LT(historical_index, GetHistorySize());
+    return events_[historical_index]->GetTouchMajor();
+  }
+
+  virtual float GetHistoricalX(size_t pointer_index,
+                               size_t historical_index) const OVERRIDE {
+    DCHECK_LT(historical_index, GetHistorySize());
+    return events_[historical_index]->GetX(pointer_index);
+  }
+
+  virtual float GetHistoricalY(size_t pointer_index,
+                               size_t historical_index) const OVERRIDE {
+    DCHECK_LT(historical_index, GetHistorySize());
+    return events_[historical_index]->GetY(pointer_index);
+  }
+
+  virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE {
+    MotionEventVector cloned_events;
+    cloned_events.reserve(events_.size());
+    for (size_t i = 0; i < events_.size(); ++i)
+      cloned_events.push_back(events_[i]->Clone().release());
+    return scoped_ptr<MotionEvent>(
+        new CompoundMotionEvent(cloned_events.Pass()));
+  }
+
+  virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE {
+    return latest().Cancel();
+  }
+
+  // Returns the new, resampled event, or NULL if none was created.
+  // TODO(jdduke): Revisit resampling to handle cases where alternating frames
+  // are resampled or resampling is otherwise inconsistent, e.g., a 90hz input
+  // and 60hz frame signal could phase-align such that even frames yield an
+  // extrapolated event and odd frames are not resampled, crbug.com/399381.
+  const MotionEvent* TryResample(base::TimeTicks resample_time,
+                                 const ui::MotionEvent* next) {
+    DCHECK_EQ(GetAction(), ACTION_MOVE);
+    const ui::MotionEvent* event0 = NULL;
+    const ui::MotionEvent* event1 = NULL;
+    if (next) {
+      DCHECK(resample_time < next->GetEventTime());
+      // Interpolate between current sample and future sample.
+      event0 = events_.back();
+      event1 = next;
+    } else if (events_.size() >= 2) {
+      // Extrapolate future sample using current sample and past sample.
+      event0 = events_[events_.size() - 2];
+      event1 = events_[events_.size() - 1];
+
+      const base::TimeTicks time1 = event1->GetEventTime();
+      base::TimeTicks max_predict =
+          time1 +
+          std::min((event1->GetEventTime() - event0->GetEventTime()) / 2,
+                   base::TimeDelta::FromMilliseconds(kResampleMaxPredictionMs));
+      if (resample_time > max_predict) {
+        TRACE_EVENT_INSTANT2("input",
+                             "MotionEventBuffer::TryResample prediction adjust",
+                             TRACE_EVENT_SCOPE_THREAD,
+                             "original(ms)",
+                             (resample_time - time1).InMilliseconds(),
+                             "adjusted(ms)",
+                             (max_predict - time1).InMilliseconds());
+        resample_time = max_predict;
+      }
+    } else {
+      TRACE_EVENT_INSTANT0("input",
+                           "MotionEventBuffer::TryResample insufficient data",
+                           TRACE_EVENT_SCOPE_THREAD);
+      return NULL;
+    }
+
+    DCHECK(event0);
+    DCHECK(event1);
+    const base::TimeTicks time0 = event0->GetEventTime();
+    const base::TimeTicks time1 = event1->GetEventTime();
+    base::TimeDelta delta = time1 - time0;
+    if (delta < base::TimeDelta::FromMilliseconds(kResampleMinDeltaMs)) {
+      TRACE_EVENT_INSTANT1("input",
+                           "MotionEventBuffer::TryResample failure",
+                           TRACE_EVENT_SCOPE_THREAD,
+                           "event_delta_too_small(ms)",
+                           delta.InMilliseconds());
+      return NULL;
+    }
+
+    events_.push_back(
+        ResampleMotionEvent(*event0, *event1, resample_time).release());
+    return events_.back();
+  }
+
+  size_t samples() const { return events_.size(); }
+
+ private:
+  const MotionEvent& latest() const { return *events_.back(); }
+
+  // Events are in order from oldest to newest.
+  MotionEventVector events_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompoundMotionEvent);
+};
+
+}  // namespace
+
+MotionEventBuffer::MotionEventBuffer(MotionEventBufferClient* client,
+                                     bool enable_resampling)
+    : client_(client), resample_(enable_resampling) {
+}
+
+MotionEventBuffer::~MotionEventBuffer() {
+}
+
+void MotionEventBuffer::OnMotionEvent(const MotionEvent& event) {
+  if (event.GetAction() != MotionEvent::ACTION_MOVE) {
+    last_extrapolated_event_time_ = base::TimeTicks();
+    if (!buffered_events_.empty())
+      FlushWithoutResampling(buffered_events_.Pass());
+    client_->ForwardMotionEvent(event);
+    return;
+  }
+
+  // Guard against events that are *older* than the last one that may have been
+  // artificially synthesized.
+  if (!last_extrapolated_event_time_.is_null()) {
+    DCHECK(buffered_events_.empty());
+    if (event.GetEventTime() < last_extrapolated_event_time_)
+      return;
+    last_extrapolated_event_time_ = base::TimeTicks();
+  }
+
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  if (buffered_events_.empty()) {
+    buffered_events_.push_back(clone.release());
+    client_->SetNeedsFlush();
+    return;
+  }
+
+  if (CanAddSample(*buffered_events_.front(), *clone)) {
+    DCHECK(buffered_events_.back()->GetEventTime() <= clone->GetEventTime());
+  } else {
+    FlushWithoutResampling(buffered_events_.Pass());
+  }
+
+  buffered_events_.push_back(clone.release());
+  // No need to request another flush as the first event will have requested it.
+}
+
+void MotionEventBuffer::Flush(base::TimeTicks frame_time) {
+  if (buffered_events_.empty())
+    return;
+
+  // Shifting the sample time back slightly minimizes the potential for
+  // misprediction when extrapolating events.
+  if (resample_)
+    frame_time -= base::TimeDelta::FromMilliseconds(kResampleLatencyMs);
+
+  // TODO(jdduke): Use a persistent MotionEventVector vector for temporary
+  // storage.
+  MotionEventVector events(
+      ConsumeSamplesNoLaterThan(&buffered_events_, frame_time));
+  if (events.empty()) {
+    DCHECK(!buffered_events_.empty());
+    client_->SetNeedsFlush();
+    return;
+  }
+
+  if (!resample_ || (events.size() == 1 && buffered_events_.empty())) {
+    FlushWithoutResampling(events.Pass());
+    if (!buffered_events_.empty())
+      client_->SetNeedsFlush();
+    return;
+  }
+
+  CompoundMotionEvent resampled_event(events.Pass());
+  base::TimeTicks original_event_time = resampled_event.GetEventTime();
+  const MotionEvent* next_event =
+      !buffered_events_.empty() ? buffered_events_.front() : NULL;
+
+  // Try to interpolate/extrapolate a new event at |frame_time|. Note that
+  // |new_event|, if non-NULL, is owned by |resampled_event_|.
+  const MotionEvent* new_event =
+      resampled_event.TryResample(frame_time, next_event);
+
+  // Log the extrapolated event time, guarding against subsequently queued
+  // events that might have an earlier timestamp.
+  if (!next_event && new_event &&
+      new_event->GetEventTime() > original_event_time) {
+    last_extrapolated_event_time_ = new_event->GetEventTime();
+  } else {
+    last_extrapolated_event_time_ = base::TimeTicks();
+  }
+
+  client_->ForwardMotionEvent(resampled_event);
+  if (!buffered_events_.empty())
+    client_->SetNeedsFlush();
+}
+
+void MotionEventBuffer::FlushWithoutResampling(MotionEventVector events) {
+  last_extrapolated_event_time_ = base::TimeTicks();
+  if (events.empty())
+    return;
+
+  if (events.size() == 1) {
+    // Avoid CompoundEvent creation to prevent unnecessary allocations.
+    scoped_ptr<MotionEvent> event(events.front());
+    events.weak_clear();
+    client_->ForwardMotionEvent(*event);
+    return;
+  }
+
+  CompoundMotionEvent compound_event(events.Pass());
+  client_->ForwardMotionEvent(compound_event);
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/motion_event_buffer.h b/ui/events/gesture_detection/motion_event_buffer.h
new file mode 100644
index 0000000..498d553
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_buffer.h
@@ -0,0 +1,79 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_BUFFER_H_
+#define UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_BUFFER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// Allows event forwarding and flush requests from a |MotionEventBuffer|.
+class MotionEventBufferClient {
+ public:
+  virtual ~MotionEventBufferClient() {}
+  virtual void ForwardMotionEvent(const MotionEvent& event) = 0;
+  virtual void SetNeedsFlush() = 0;
+};
+
+// Utility class for buffering streamed MotionEventVector until a given flush.
+// Events that can be combined will remain buffered, and depending on the flush
+// time and buffered events, a resampled event with history will be synthesized.
+// The primary purpose of this class is to ensure a smooth output motion signal
+// by resampling a discrete input signal that may run on a different frequency
+// or lack alignment with the output display signal.
+// Note that this class is largely based on code from Android's existing touch
+// pipeline (in particular, logic from ImageTransport, http://goo.gl/Ixsb0D).
+// See the design doc at http://goo.gl/MdmpCf for more details.
+class GESTURE_DETECTION_EXPORT MotionEventBuffer {
+ public:
+  // The provided |client| must not be null, and |enable_resampling| determines
+  // resampling behavior (see |resample_|).
+  MotionEventBuffer(MotionEventBufferClient* client, bool enable_resampling);
+  ~MotionEventBuffer();
+
+  // Should be called upon receipt of an event from the platform, prior to event
+  // dispatch to UI or content components. Events that can be coalesced will
+  // remain buffered until the next |Flush()|, while other events will be
+  // forwarded immediately (incidentally flushing currently buffered events).
+  void OnMotionEvent(const MotionEvent& event);
+
+  // Forward any buffered events, resampling if necessary (see |resample_|)
+  // according to the provided |frame_time|. This should be called in response
+  // to |SetNeedsFlush()| calls on the client. If the buffer is empty, no
+  // events will be forwarded, and if another flush is necessary it will be
+  // requested.
+  void Flush(base::TimeTicks frame_time);
+
+ private:
+  typedef ScopedVector<MotionEvent> MotionEventVector;
+
+  void FlushWithoutResampling(MotionEventVector events);
+
+  MotionEventBufferClient* const client_;
+  MotionEventVector buffered_events_;
+
+  // Time of the most recently extrapolated event. This will be 0 if the
+  // last sent event was not extrapolated. Used internally to guard against
+  // conflicts between events received from the platfrom that may have an
+  // earlier timestamp than that synthesized at the latest resample.
+  base::TimeTicks last_extrapolated_event_time_;
+
+  // Whether buffered events should be resampled upon |Flush()|. If true, short
+  // horizon interpolation/extrapolation will be used to synthesize the
+  // forwarded event. Otherwise the most recently buffered event will be
+  // forwarded, with preceding events as historical entries. Defaults to true.
+  bool resample_;
+
+  DISALLOW_COPY_AND_ASSIGN(MotionEventBuffer);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_BUFFER_H_
diff --git a/ui/events/gesture_detection/motion_event_buffer_unittest.cc b/ui/events/gesture_detection/motion_event_buffer_unittest.cc
new file mode 100644
index 0000000..f51897f
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_buffer_unittest.cc
@@ -0,0 +1,871 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/motion_event_buffer.h"
+#include "ui/events/test/mock_motion_event.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::test::MockMotionEvent;
+
+namespace ui {
+
+const int kSmallDeltaMs = 1;
+const int kLargeDeltaMs = 50;
+const int kResampleDeltaMs = 5;
+const float kVelocityEpsilon = 0.01f;
+const float kDeltaEpsilon = 0.1f;
+
+#define EXPECT_EVENT_EQ(A, B)         \
+  {                                   \
+    SCOPED_TRACE(testing::Message()); \
+    ExpectEquals((A), (B));           \
+  }
+#define EXPECT_EVENT_IGNORING_HISTORY_EQ(A, B) \
+  {                                            \
+    SCOPED_TRACE(testing::Message());          \
+    ExpectEqualsIgnoringHistory((A), (B));     \
+  }
+#define EXPECT_EVENT_HISTORY_EQ(A, I, B)     \
+  {                                          \
+    SCOPED_TRACE(testing::Message());        \
+    ExpectEqualsHistoryIndex((A), (I), (B)); \
+  }
+
+class MotionEventBufferTest : public testing::Test,
+                              public MotionEventBufferClient {
+ public:
+  MotionEventBufferTest() : needs_flush_(false) {}
+  virtual ~MotionEventBufferTest() {}
+
+  // MotionEventBufferClient implementation.
+  virtual void ForwardMotionEvent(const MotionEvent& event) OVERRIDE {
+    forwarded_events_.push_back(event.Clone().release());
+  }
+
+  virtual void SetNeedsFlush() OVERRIDE { needs_flush_ = true; }
+
+  bool GetAndResetNeedsFlush() {
+    bool needs_flush = needs_flush_;
+    needs_flush_ = false;
+    return needs_flush;
+  }
+
+  ScopedVector<MotionEvent> GetAndResetForwardedEvents() {
+    ScopedVector<MotionEvent> forwarded_events;
+    forwarded_events.swap(forwarded_events_);
+    return forwarded_events.Pass();
+  }
+
+  const MotionEvent* GetLastEvent() const {
+    return forwarded_events_.empty() ? NULL : forwarded_events_.back();
+  }
+
+  static base::TimeDelta LargeDelta() {
+    return base::TimeDelta::FromMilliseconds(kLargeDeltaMs);
+  }
+
+  static base::TimeDelta SmallDelta() {
+    return base::TimeDelta::FromMilliseconds(kSmallDeltaMs);
+  }
+
+  static base::TimeDelta ResampleDelta() {
+    return base::TimeDelta::FromMilliseconds(kResampleDeltaMs);
+  }
+
+  static void ExpectEqualsImpl(const MotionEvent& a,
+                               const MotionEvent& b,
+                               bool ignore_history) {
+    EXPECT_EQ(a.GetId(), b.GetId());
+    EXPECT_EQ(a.GetAction(), b.GetAction());
+    EXPECT_EQ(a.GetActionIndex(), b.GetActionIndex());
+    EXPECT_EQ(a.GetButtonState(), b.GetButtonState());
+    EXPECT_EQ(a.GetEventTime(), b.GetEventTime());
+
+    ASSERT_EQ(a.GetPointerCount(), b.GetPointerCount());
+    for (size_t i = 0; i < a.GetPointerCount(); ++i) {
+      int bi = b.FindPointerIndexOfId(a.GetPointerId(i));
+      ASSERT_NE(bi, -1);
+      EXPECT_EQ(a.GetX(i), b.GetX(bi));
+      EXPECT_EQ(a.GetY(i), b.GetY(bi));
+      EXPECT_EQ(a.GetRawX(i), b.GetRawX(bi));
+      EXPECT_EQ(a.GetRawY(i), b.GetRawY(bi));
+      EXPECT_EQ(a.GetTouchMajor(i), b.GetTouchMajor(bi));
+      EXPECT_EQ(a.GetTouchMinor(i), b.GetTouchMinor(bi));
+      EXPECT_EQ(a.GetOrientation(i), b.GetOrientation(bi));
+      EXPECT_EQ(a.GetPressure(i), b.GetPressure(bi));
+      EXPECT_EQ(a.GetToolType(i), b.GetToolType(bi));
+    }
+
+    if (ignore_history)
+      return;
+
+    ASSERT_EQ(a.GetHistorySize(), b.GetHistorySize());
+    for (size_t h = 0; h < a.GetHistorySize(); ++h)
+      ExpectEqualsHistoryIndex(a, h, b);
+  }
+
+  // Verify that all public data of |a|, excluding history, equals that of |b|.
+  static void ExpectEqualsIgnoringHistory(const MotionEvent& a,
+                                          const MotionEvent& b) {
+    const bool ignore_history = true;
+    ExpectEqualsImpl(a, b, ignore_history);
+  }
+
+  // Verify that all public data of |a| equals that of |b|.
+  static void ExpectEquals(const MotionEvent& a, const MotionEvent& b) {
+    const bool ignore_history = false;
+    ExpectEqualsImpl(a, b, ignore_history);
+  }
+
+  // Verify that the historical data of |a| given by |historical_index|
+  // corresponds to the *raw* data of |b|.
+  static void ExpectEqualsHistoryIndex(const MotionEvent& a,
+                                       size_t history_index,
+                                       const MotionEvent& b) {
+    ASSERT_LT(history_index, a.GetHistorySize());
+    EXPECT_EQ(a.GetPointerCount(), b.GetPointerCount());
+    EXPECT_TRUE(a.GetHistoricalEventTime(history_index) == b.GetEventTime());
+
+    for (size_t i = 0; i < a.GetPointerCount(); ++i) {
+      int bi = b.FindPointerIndexOfId(a.GetPointerId(i));
+      ASSERT_NE(bi, -1);
+      EXPECT_EQ(a.GetHistoricalX(i, history_index), b.GetX(bi));
+      EXPECT_EQ(a.GetHistoricalY(i, history_index), b.GetY(bi));
+      EXPECT_EQ(a.GetHistoricalTouchMajor(i, history_index),
+                b.GetTouchMajor(bi));
+    }
+  }
+
+ protected:
+  void RunResample(base::TimeDelta flush_time_delta,
+                   base::TimeDelta event_time_delta) {
+    for (base::TimeDelta offset; offset < event_time_delta;
+         offset += event_time_delta / 3) {
+      SCOPED_TRACE(testing::Message()
+                   << "Resample(offset="
+                   << static_cast<int>(offset.InMilliseconds()) << "ms)");
+      RunResample(flush_time_delta, event_time_delta, offset);
+    }
+  }
+
+  // Given an event and flush sampling frequency, inject a stream of events,
+  // flushing at appropriate points in the stream. Verify that the continuous
+  // velocity sampled by the *input* stream matches the discrete velocity
+  // as computed from the resampled *output* stream.
+  void RunResample(base::TimeDelta flush_time_delta,
+                   base::TimeDelta event_time_delta,
+                   base::TimeDelta event_time_offset) {
+    base::TimeTicks event_time = base::TimeTicks::Now();
+    base::TimeTicks flush_time =
+        event_time + flush_time_delta - event_time_offset;
+    base::TimeTicks max_event_time =
+        event_time + base::TimeDelta::FromSecondsD(0.5f);
+    const size_t min_expected_events =
+        static_cast<size_t>((max_event_time - flush_time) /
+                            std::max(event_time_delta, flush_time_delta));
+
+    MotionEventBuffer buffer(this, true);
+
+    gfx::Vector2dF velocity(33.f, -11.f);
+    gfx::PointF position(17.f, 42.f);
+    scoped_ptr<MotionEvent> last_flushed_event;
+    size_t events = 0;
+    float last_dx = 0, last_dy = 0;
+    base::TimeDelta last_dt;
+    while (event_time < max_event_time) {
+      position += gfx::ScaleVector2d(velocity, event_time_delta.InSecondsF());
+      MockMotionEvent move(
+          MotionEvent::ACTION_MOVE, event_time, position.x(), position.y());
+      buffer.OnMotionEvent(move);
+      event_time += event_time_delta;
+
+      while (flush_time < event_time) {
+        buffer.Flush(flush_time);
+        flush_time += flush_time_delta;
+        const MotionEvent* current_flushed_event = GetLastEvent();
+        if (current_flushed_event) {
+          if (!last_flushed_event) {
+            last_flushed_event = current_flushed_event->Clone();
+            continue;
+          }
+
+          base::TimeDelta dt = current_flushed_event->GetEventTime() -
+                               last_flushed_event->GetEventTime();
+          EXPECT_GE(dt.ToInternalValue(), 0);
+          // A time delta of 0 is possible if the flush rate is greater than the
+          // event rate, in which case we can simply skip forward.
+          if (dt == base::TimeDelta())
+            continue;
+
+          const float dx =
+              current_flushed_event->GetX() - last_flushed_event->GetX();
+          const float dy =
+              current_flushed_event->GetY() - last_flushed_event->GetY();
+          const float dt_s = (current_flushed_event->GetEventTime() -
+                              last_flushed_event->GetEventTime()).InSecondsF();
+
+          // The discrete velocity should mirror the constant velocity.
+          EXPECT_NEAR(velocity.x(), dx / dt_s, kVelocityEpsilon);
+          EXPECT_NEAR(velocity.y(), dy / dt_s, kVelocityEpsilon);
+
+          // The impulse delta for each frame should remain constant.
+          if (last_dy)
+            EXPECT_NEAR(dx, last_dx, kDeltaEpsilon);
+          if (last_dy)
+            EXPECT_NEAR(dy, last_dy, kDeltaEpsilon);
+
+          // The timestamp delta should remain constant.
+          if (last_dt != base::TimeDelta())
+            EXPECT_TRUE((dt - last_dt).InMillisecondsF() < kDeltaEpsilon);
+
+          last_dx = dx;
+          last_dy = dy;
+          last_dt = dt;
+          last_flushed_event = current_flushed_event->Clone();
+          events += GetAndResetForwardedEvents().size();
+        }
+      }
+    }
+    events += GetAndResetForwardedEvents().size();
+    EXPECT_GE(events, min_expected_events);
+  }
+
+ private:
+  ScopedVector<MotionEvent> forwarded_events_;
+  bool needs_flush_;
+};
+
+TEST_F(MotionEventBufferTest, BufferEmpty) {
+  MotionEventBuffer buffer(this, true);
+
+  buffer.Flush(base::TimeTicks::Now());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, BufferWithOneMoveNotResampled) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move(MotionEvent::ACTION_MOVE, event_time, 4.f, 4.f);
+  buffer.OnMotionEvent(move);
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+
+  buffer.Flush(event_time + ResampleDelta());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move, *GetLastEvent());
+  EXPECT_EQ(1U, GetAndResetForwardedEvents().size());
+}
+
+TEST_F(MotionEventBufferTest, BufferFlushedOnNonActionMove) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+  buffer.OnMotionEvent(move0);
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+
+  // The second move should remain buffered.
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+  buffer.OnMotionEvent(move1);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+
+  // The third move should remain buffered.
+  MockMotionEvent move2(MotionEvent::ACTION_MOVE, event_time, 3.f, 3.f);
+  buffer.OnMotionEvent(move2);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+
+  // The up should flush the buffer.
+  MockMotionEvent up(MotionEvent::ACTION_UP, event_time, 4.f, 4.f);
+  buffer.OnMotionEvent(up);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // The flushed events should include the up and the moves, with the latter
+  // combined into a single event with history.
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(2U, events.size());
+  EXPECT_EVENT_EQ(up, *events.back());
+  EXPECT_EQ(2U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move2);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 1, move1);
+}
+
+TEST_F(MotionEventBufferTest, BufferFlushedOnIncompatibleActionMove) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+  buffer.OnMotionEvent(move0);
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+
+  // The second move has a different pointer count, flushing the first.
+  MockMotionEvent move1(
+      MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f, 3.f, 3.f);
+  buffer.OnMotionEvent(move1);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move0, *GetLastEvent());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+
+  // The third move has differing tool types, flushing the second.
+  MockMotionEvent move2(move1);
+  move2.SetToolType(0, MotionEvent::TOOL_TYPE_STYLUS);
+  buffer.OnMotionEvent(move2);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  EXPECT_EVENT_EQ(move1, *GetLastEvent());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+
+  // The flushed event should only include the latest move event.
+  buffer.Flush(event_time);
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(3U, events.size());
+  EXPECT_EVENT_EQ(move2, *events.back());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+
+  // Events with different pointer ids should not combine.
+  PointerProperties pointer0(5.f, 5.f);
+  pointer0.id = 1;
+  PointerProperties pointer1(10.f, 10.f);
+  pointer1.id = 2;
+  MotionEventGeneric move3(MotionEvent::ACTION_MOVE, event_time, pointer0);
+  move3.PushPointer(pointer1);
+  buffer.OnMotionEvent(move3);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  MotionEventGeneric move4(MotionEvent::ACTION_MOVE, event_time, pointer0);
+  pointer1.id = 7;
+  move4.PushPointer(pointer1);
+  buffer.OnMotionEvent(move2);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move3, *GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, OnlyActionMoveBuffered) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent down(MotionEvent::ACTION_DOWN, event_time, 1.f, 1.f);
+  buffer.OnMotionEvent(down);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(down, *GetLastEvent());
+
+  GetAndResetForwardedEvents();
+
+  MockMotionEvent up(MotionEvent::ACTION_UP, event_time, 2.f, 2.f);
+  buffer.OnMotionEvent(up);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(up, *GetLastEvent());
+
+  GetAndResetForwardedEvents();
+
+  MockMotionEvent cancel(MotionEvent::ACTION_CANCEL, event_time, 3.f, 3.f);
+  buffer.OnMotionEvent(cancel);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(cancel, *GetLastEvent());
+
+  GetAndResetForwardedEvents();
+
+  MockMotionEvent move(MotionEvent::ACTION_MOVE, event_time, 4.f, 4.f);
+  buffer.OnMotionEvent(move);
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+  EXPECT_FALSE(GetLastEvent());
+
+  base::TimeTicks flush_time = move.GetEventTime() + ResampleDelta();
+  buffer.Flush(flush_time);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move, *GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, OutOfOrderPointersBuffered) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  PointerProperties p0(1.f, 2.f);
+  p0.id = 1;
+  PointerProperties p1(2.f, 1.f);
+  p1.id = 2;
+
+  MotionEventGeneric move0(MotionEvent::ACTION_MOVE, event_time, p0);
+  move0.PushPointer(p1);
+  buffer.OnMotionEvent(move0);
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+  ASSERT_FALSE(GetLastEvent());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+
+  // The second move should remain buffered even if the logical pointers are
+  // in a different order.
+  MotionEventGeneric move1(MotionEvent::ACTION_MOVE, event_time, p1);
+  move1.PushPointer(p0);
+  buffer.OnMotionEvent(move1);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_FALSE(GetLastEvent());
+
+  // As the two events are logically the same but for ordering and time, the
+  // synthesized event should yield a logically identical event.
+  base::TimeTicks flush_time = move1.GetEventTime() + ResampleDelta();
+  buffer.Flush(flush_time);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(move1, *events.front());
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, FlushedEventsNeverLaterThanFlushTime) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += LargeDelta();
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // A flush occurring too early should not forward any events.
+  base::TimeTicks flush_time = move0.GetEventTime() - ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // With resampling enabled, a flush occurring before the resample
+  // offset should not forward any events.
+  flush_time = move0.GetEventTime();
+  buffer.Flush(flush_time);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // Only the first event should get flushed, as the flush timestamp precedes
+  // the second's timestamp by a sufficient amount (preventing interpolation).
+  flush_time = move0.GetEventTime() + ResampleDelta();
+  buffer.Flush(flush_time);
+
+  // There should only be one flushed event.
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_TRUE(GetLastEvent()->GetEventTime() <= flush_time);
+  GetAndResetForwardedEvents();
+
+  // Flushing again with a similar timestamp should have no effect other than
+  // triggering another flush request.
+  flush_time += base::TimeDelta::FromMilliseconds(1);
+  buffer.Flush(flush_time);
+  EXPECT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // Flushing after the second move's time should trigger forwarding.
+  flush_time = move1.GetEventTime() + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move1, *GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWhenDisabled) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  const bool resampling_enabled = false;
+  MotionEventBuffer buffer(this, resampling_enabled);
+
+  // Queue two events.
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 15.f, 30.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Flush at a time between the first and second events.
+  base::TimeTicks interpolated_time =
+      move0.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 2;
+  base::TimeTicks flush_time = interpolated_time;
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, with the second remaining buffered
+  // and no resampling having occurred.
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EVENT_EQ(move0, *events.front());
+
+  // The second move should be flushed without resampling.
+  flush_time = move1.GetEventTime();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move1, *GetLastEvent());
+  GetAndResetForwardedEvents();
+
+  // Now queue two more events.
+  move0 = MockMotionEvent(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += base::TimeDelta::FromMilliseconds(5);
+  move1 = MockMotionEvent(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Sample at a time beyond the first and second events.
+  flush_time =
+      move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime());
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, with the first event in the history
+  // and the second event as the actual event data (no resampling).
+  events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(1U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move1);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWithOutOfOrderActionMove) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += base::TimeDelta::FromMilliseconds(10);
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Sample at a time beyond the first and second events.
+  base::TimeTicks extrapolated_time =
+      move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime());
+  base::TimeTicks flush_time = extrapolated_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, with the event extrapolated from
+  // the two events.
+  base::TimeTicks expected_time =
+      move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 2;
+  ScopedVector<MotionEvent> events0 = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events0.size());
+  EXPECT_EQ(2U, events0.front()->GetHistorySize());
+  EXPECT_EQ(expected_time, events0.front()->GetEventTime());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Try enqueuing an event *after* the second event but *before* the
+  // extrapolated event. It should be dropped.
+  event_time = move1.GetEventTime() + base::TimeDelta::FromMilliseconds(1);
+  MockMotionEvent move2(MotionEvent::ACTION_MOVE, event_time, 15.f, 25.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Finally queue an event *after* the extrapolated event.
+  event_time = expected_time + base::TimeDelta::FromMilliseconds(1);
+  MockMotionEvent move3(MotionEvent::ACTION_MOVE, event_time, 15.f, 25.f);
+  buffer.OnMotionEvent(move3);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The flushed event should simply be the latest event.
+  flush_time = event_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  ScopedVector<MotionEvent> events1 = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events1.size());
+  EXPECT_EVENT_EQ(move3, *events1.front());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWithSmallTimeDeltaBetweenMoves) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  // The first move should be buffered.
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += SmallDelta();
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  base::TimeTicks flush_time = event_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, and no resampling should have
+  // occured between the first and the second as they were temporally too close.
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(1U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move1);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, NoResamplingWithMismatchBetweenMoves) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  // The first move should be buffered.
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 1.f, 1.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += SmallDelta();
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 2.f, 2.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  base::TimeTicks flush_time = event_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, and no resampling should have
+  // occured between the first and the second as they were temporally too close.
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(1U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), move1);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+}
+
+TEST_F(MotionEventBufferTest, Interpolation) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += base::TimeDelta::FromMilliseconds(5);
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 15.f, 30.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Sample at a time between the first and second events.
+  base::TimeTicks interpolated_time =
+      move0.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 3;
+  base::TimeTicks flush_time = interpolated_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, with the event interpolated between
+  // the two events. The second event should remain buffered.
+  float alpha = (interpolated_time - move0.GetEventTime()).InMillisecondsF() /
+                (move1.GetEventTime() - move0.GetEventTime()).InMillisecondsF();
+  MockMotionEvent interpolated_event(
+      MotionEvent::ACTION_MOVE,
+      interpolated_time,
+      move0.GetX(0) + (move1.GetX(0) - move0.GetX(0)) * alpha,
+      move0.GetY(0) + (move1.GetY(0) - move0.GetY(0)) * alpha);
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(1U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), interpolated_event);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+
+  // The second move should be flushed without resampling.
+  flush_time = move1.GetEventTime() + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_EVENT_EQ(move1, *GetLastEvent());
+}
+
+TEST_F(MotionEventBufferTest, Extrapolation) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += base::TimeDelta::FromMilliseconds(5);
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Sample at a time beyond the first and second events.
+  base::TimeTicks extrapolated_time =
+      move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime());
+  base::TimeTicks flush_time = extrapolated_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, with the event extrapolated from
+  // the two events. The first and second events should be in the history.
+  // Note that the maximum extrapolation is limited by *half* of the time delta
+  // between the two events, hence we divide the relative delta by 2 in
+  // determining the extrapolated event.
+  base::TimeTicks expected_time =
+      move1.GetEventTime() + (move1.GetEventTime() - move0.GetEventTime()) / 2;
+  float expected_alpha =
+      (expected_time - move0.GetEventTime()).InMillisecondsF() /
+      (move1.GetEventTime() - move0.GetEventTime()).InMillisecondsF();
+  MockMotionEvent extrapolated_event(
+      MotionEvent::ACTION_MOVE,
+      expected_time,
+      move0.GetX(0) + (move1.GetX(0) - move0.GetX(0)) * expected_alpha,
+      move0.GetY(0) + (move1.GetY(0) - move0.GetY(0)) * expected_alpha);
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(2U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), extrapolated_event);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 1, move1);
+}
+
+TEST_F(MotionEventBufferTest, ExtrapolationHorizonLimited) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventBuffer buffer(this, true);
+
+  MockMotionEvent move0(MotionEvent::ACTION_MOVE, event_time, 5.f, 10.f);
+  buffer.OnMotionEvent(move0);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_TRUE(GetAndResetNeedsFlush());
+
+  // The second move should remain buffered.
+  event_time += base::TimeDelta::FromMilliseconds(24);
+  MockMotionEvent move1(MotionEvent::ACTION_MOVE, event_time, 10.f, 20.f);
+  buffer.OnMotionEvent(move1);
+  ASSERT_FALSE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // Sample at a time beyond the first and second events.
+  base::TimeTicks extrapolated_time =
+      event_time + base::TimeDelta::FromMilliseconds(24);
+  base::TimeTicks flush_time = extrapolated_time + ResampleDelta();
+  buffer.Flush(flush_time);
+  ASSERT_TRUE(GetLastEvent());
+  EXPECT_FALSE(GetAndResetNeedsFlush());
+
+  // There should only be one flushed event, with the event extrapolated from
+  // the two events. The first and second events should be in the history.
+  // Note that the maximum extrapolation is limited by 8 ms.
+  base::TimeTicks expected_time =
+      move1.GetEventTime() + base::TimeDelta::FromMilliseconds(8);
+  float expected_alpha =
+      (expected_time - move0.GetEventTime()).InMillisecondsF() /
+      (move1.GetEventTime() - move0.GetEventTime()).InMillisecondsF();
+  MockMotionEvent extrapolated_event(
+      MotionEvent::ACTION_MOVE,
+      expected_time,
+      move0.GetX(0) + (move1.GetX(0) - move0.GetX(0)) * expected_alpha,
+      move0.GetY(0) + (move1.GetY(0) - move0.GetY(0)) * expected_alpha);
+  ScopedVector<MotionEvent> events = GetAndResetForwardedEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ(2U, events.front()->GetHistorySize());
+  EXPECT_EVENT_IGNORING_HISTORY_EQ(*events.front(), extrapolated_event);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 0, move0);
+  EXPECT_EVENT_HISTORY_EQ(*events.front(), 1, move1);
+}
+
+TEST_F(MotionEventBufferTest, ResamplingWithReorderedPointers) {
+
+}
+
+TEST_F(MotionEventBufferTest, Resampling30to60) {
+  base::TimeDelta flush_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 60.);
+  base::TimeDelta event_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 30.);
+
+  RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling60to60) {
+  base::TimeDelta flush_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 60.);
+  base::TimeDelta event_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 60.);
+
+  RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling100to60) {
+  base::TimeDelta flush_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 60.);
+  base::TimeDelta event_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 100.);
+
+  RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling120to60) {
+  base::TimeDelta flush_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 60.);
+  base::TimeDelta event_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 120.);
+
+  RunResample(flush_time_delta, event_time_delta);
+}
+
+TEST_F(MotionEventBufferTest, Resampling150to60) {
+  base::TimeDelta flush_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 60.);
+  base::TimeDelta event_time_delta =
+      base::TimeDelta::FromMillisecondsD(1000. / 150.);
+
+  RunResample(flush_time_delta, event_time_delta);
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/motion_event_generic.cc b/ui/events/gesture_detection/motion_event_generic.cc
new file mode 100644
index 0000000..afda52a
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_generic.cc
@@ -0,0 +1,168 @@
+// Copyright 2014 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 "ui/events/gesture_detection/motion_event_generic.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+PointerProperties::PointerProperties()
+    : id(0),
+      tool_type(MotionEvent::TOOL_TYPE_UNKNOWN),
+      x(0),
+      y(0),
+      raw_x(0),
+      raw_y(0),
+      pressure(0),
+      touch_major(0),
+      touch_minor(0),
+      orientation(0) {
+}
+
+PointerProperties::PointerProperties(float x, float y)
+    : id(0),
+      tool_type(MotionEvent::TOOL_TYPE_UNKNOWN),
+      x(x),
+      y(y),
+      raw_x(x),
+      raw_y(y),
+      pressure(0),
+      touch_major(0),
+      touch_minor(0),
+      orientation(0) {
+}
+
+MotionEventGeneric::MotionEventGeneric()
+    : action_(ACTION_CANCEL),
+      id_(0),
+      action_index_(0),
+      button_state_(0),
+      flags_(0) {
+}
+
+MotionEventGeneric::MotionEventGeneric(Action action,
+                                       base::TimeTicks event_time,
+                                       const PointerProperties& pointer)
+    : action_(action),
+      event_time_(event_time),
+      id_(0),
+      action_index_(0),
+      button_state_(0),
+      flags_(0) {
+  PushPointer(pointer);
+}
+
+MotionEventGeneric::MotionEventGeneric(const MotionEventGeneric& other)
+    : action_(other.action_),
+      event_time_(other.event_time_),
+      id_(other.id_),
+      action_index_(other.action_index_),
+      button_state_(other.button_state_),
+      flags_(other.flags_),
+      pointers_(other.pointers_) {
+}
+
+MotionEventGeneric::~MotionEventGeneric() {
+}
+
+int MotionEventGeneric::GetId() const {
+  return id_;
+}
+
+MotionEvent::Action MotionEventGeneric::GetAction() const {
+  return action_;
+}
+
+int MotionEventGeneric::GetActionIndex() const {
+  return action_index_;
+}
+
+size_t MotionEventGeneric::GetPointerCount() const {
+  return pointers_->size();
+}
+
+int MotionEventGeneric::GetPointerId(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].id;
+}
+
+float MotionEventGeneric::GetX(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].x;
+}
+
+float MotionEventGeneric::GetY(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].y;
+}
+
+float MotionEventGeneric::GetRawX(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].raw_x;
+}
+
+float MotionEventGeneric::GetRawY(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].raw_y;
+}
+
+float MotionEventGeneric::GetTouchMajor(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].touch_major;
+}
+
+float MotionEventGeneric::GetTouchMinor(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].touch_minor;
+}
+
+float MotionEventGeneric::GetOrientation(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].orientation;
+}
+
+float MotionEventGeneric::GetPressure(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].pressure;
+}
+
+MotionEvent::ToolType MotionEventGeneric::GetToolType(
+    size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointers_->size());
+  return pointers_[pointer_index].tool_type;
+}
+
+int MotionEventGeneric::GetButtonState() const {
+  return button_state_;
+}
+
+int MotionEventGeneric::GetFlags() const {
+  return flags_;
+}
+
+base::TimeTicks MotionEventGeneric::GetEventTime() const {
+  return event_time_;
+}
+
+scoped_ptr<MotionEvent> MotionEventGeneric::Clone() const {
+  return scoped_ptr<MotionEvent>(new MotionEventGeneric(*this));
+}
+
+scoped_ptr<MotionEvent> MotionEventGeneric::Cancel() const {
+  scoped_ptr<MotionEventGeneric> event(new MotionEventGeneric(*this));
+  event->set_action(ACTION_CANCEL);
+  return event.PassAs<MotionEvent>();
+}
+
+void MotionEventGeneric::PushPointer(const PointerProperties& pointer) {
+  pointers_->push_back(pointer);
+}
+
+void MotionEventGeneric::PopPointer() {
+  DCHECK_GT(pointers_->size(), 0U);
+  pointers_->pop_back();
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/motion_event_generic.h b/ui/events/gesture_detection/motion_event_generic.h
new file mode 100644
index 0000000..2750cb2
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_generic.h
@@ -0,0 +1,95 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_GENERIC_H_
+#define UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_GENERIC_H_
+
+#include "base/basictypes.h"
+#include "base/containers/stack_container.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+
+struct GESTURE_DETECTION_EXPORT PointerProperties {
+  PointerProperties();
+  PointerProperties(float x, float y);
+
+  int id;
+  MotionEvent::ToolType tool_type;
+  float x;
+  float y;
+  float raw_x;
+  float raw_y;
+  float pressure;
+  float touch_major;
+  float touch_minor;
+  float orientation;
+};
+
+// A generic MotionEvent implementation.
+class GESTURE_DETECTION_EXPORT MotionEventGeneric : public MotionEvent {
+ public:
+  MotionEventGeneric(Action action,
+                     base::TimeTicks event_time,
+                     const PointerProperties& pointer);
+  MotionEventGeneric(const MotionEventGeneric& other);
+
+  virtual ~MotionEventGeneric();
+
+  // MotionEvent implementation.
+  virtual int GetId() const OVERRIDE;
+  virtual Action GetAction() const OVERRIDE;
+  virtual int GetActionIndex() const OVERRIDE;
+  virtual size_t GetPointerCount() const OVERRIDE;
+  virtual int GetPointerId(size_t pointer_index) const OVERRIDE;
+  virtual float GetX(size_t pointer_index) const OVERRIDE;
+  virtual float GetY(size_t pointer_index) const OVERRIDE;
+  virtual float GetRawX(size_t pointer_index) const OVERRIDE;
+  virtual float GetRawY(size_t pointer_index) const OVERRIDE;
+  virtual float GetTouchMajor(size_t pointer_index) const OVERRIDE;
+  virtual float GetTouchMinor(size_t pointer_index) const OVERRIDE;
+  virtual float GetOrientation(size_t pointer_index) const OVERRIDE;
+  virtual float GetPressure(size_t pointer_index) const OVERRIDE;
+  virtual ToolType GetToolType(size_t pointer_index) const OVERRIDE;
+  virtual int GetButtonState() const OVERRIDE;
+  virtual int GetFlags() const OVERRIDE;
+  virtual base::TimeTicks GetEventTime() const OVERRIDE;
+  virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE;
+  virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE;
+
+  void PushPointer(const PointerProperties& pointer);
+
+  void set_action(Action action) { action_ = action; }
+  void set_event_time(base::TimeTicks event_time) { event_time_ = event_time; }
+  void set_id(int id) { id_ = id; }
+  void set_action_index(int action_index) { action_index_ = action_index; }
+  void set_button_state(int button_state) { button_state_ = button_state; }
+  void set_flags(int flags) { flags_ = flags; }
+
+ protected:
+  MotionEventGeneric();
+
+  void PopPointer();
+
+  PointerProperties& pointer(size_t index) { return pointers_[index]; }
+  const PointerProperties& pointer(size_t index) const {
+    return pointers_[index];
+  }
+
+ private:
+  enum { kTypicalMaxPointerCount = 5 };
+
+  Action action_;
+  base::TimeTicks event_time_;
+  int id_;
+  int action_index_;
+  int button_state_;
+  int flags_;
+  base::StackVector<PointerProperties, kTypicalMaxPointerCount> pointers_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_GENERIC_H_
diff --git a/ui/events/gesture_detection/motion_event_generic_unittest.cc b/ui/events/gesture_detection/motion_event_generic_unittest.cc
new file mode 100644
index 0000000..f1aa293
--- /dev/null
+++ b/ui/events/gesture_detection/motion_event_generic_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 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 "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_detection/motion_event_generic.h"
+
+namespace ui {
+
+TEST(MotionEventGenericTest, Basic) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  MotionEventGeneric event(
+      MotionEvent::ACTION_DOWN, event_time, PointerProperties());
+  EXPECT_EQ(1U, event.GetPointerCount());
+  EXPECT_EQ(0U, event.GetHistorySize());
+  EXPECT_EQ(event_time, event.GetEventTime());
+
+  event.PushPointer(PointerProperties(8.3f, 4.7f));
+  ASSERT_EQ(2U, event.GetPointerCount());
+  EXPECT_EQ(8.3f, event.GetX(1));
+  EXPECT_EQ(4.7f, event.GetY(1));
+
+  event.PushPointer(PointerProperties(2.3f, -3.7f));
+  ASSERT_EQ(3U, event.GetPointerCount());
+  EXPECT_EQ(2.3f, event.GetX(2));
+  EXPECT_EQ(-3.7f, event.GetY(2));
+
+  event.set_id(1);
+  EXPECT_EQ(1, event.GetId());
+
+  event.set_action(MotionEvent::ACTION_POINTER_DOWN);
+  EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction());
+
+  event_time += base::TimeDelta::FromMilliseconds(5);
+  event.set_event_time(event_time);
+  EXPECT_EQ(event_time, event.GetEventTime());
+
+  event.set_button_state(MotionEvent::BUTTON_PRIMARY);
+  EXPECT_EQ(MotionEvent::BUTTON_PRIMARY, event.GetButtonState());
+
+  event.set_flags(EF_ALT_DOWN | EF_SHIFT_DOWN);
+  EXPECT_EQ(EF_ALT_DOWN | EF_SHIFT_DOWN, event.GetFlags());
+
+  event.set_action_index(1);
+  EXPECT_EQ(1, event.GetActionIndex());
+}
+
+TEST(MotionEventGenericTest, Clone) {
+  MotionEventGeneric event(MotionEvent::ACTION_DOWN,
+                           base::TimeTicks::Now(),
+                           PointerProperties(8.3f, 4.7f));
+  event.set_id(1);
+  event.set_button_state(MotionEvent::BUTTON_PRIMARY);
+
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  ASSERT_TRUE(clone);
+  EXPECT_EQ(event, *clone);
+}
+
+TEST(MotionEventGenericTest, Cancel) {
+  MotionEventGeneric event(MotionEvent::ACTION_UP,
+                           base::TimeTicks::Now(),
+                           PointerProperties(8.7f, 4.3f));
+  event.set_id(2);
+  event.set_button_state(MotionEvent::BUTTON_SECONDARY);
+
+  scoped_ptr<MotionEvent> cancel = event.Cancel();
+  event.set_action(MotionEvent::ACTION_CANCEL);
+  ASSERT_TRUE(cancel);
+  EXPECT_EQ(event, *cancel);
+}
+
+TEST(MotionEventGenericTest, FindPointerIndexOfId) {
+  base::TimeTicks event_time = base::TimeTicks::Now();
+  PointerProperties pointer;
+  pointer.id = 0;
+  MotionEventGeneric event0(MotionEvent::ACTION_DOWN, event_time, pointer);
+  EXPECT_EQ(0, event0.FindPointerIndexOfId(0));
+  EXPECT_EQ(-1, event0.FindPointerIndexOfId(1));
+  EXPECT_EQ(-1, event0.FindPointerIndexOfId(-1));
+
+  MotionEventGeneric event1(event0);
+  pointer.id = 7;
+  event1.PushPointer(pointer);
+  EXPECT_EQ(0, event1.FindPointerIndexOfId(0));
+  EXPECT_EQ(1, event1.FindPointerIndexOfId(7));
+  EXPECT_EQ(-1, event1.FindPointerIndexOfId(6));
+  EXPECT_EQ(-1, event1.FindPointerIndexOfId(1));
+
+  MotionEventGeneric event2(event1);
+  pointer.id = 3;
+  event2.PushPointer(pointer);
+  EXPECT_EQ(0, event2.FindPointerIndexOfId(0));
+  EXPECT_EQ(1, event2.FindPointerIndexOfId(7));
+  EXPECT_EQ(2, event2.FindPointerIndexOfId(3));
+  EXPECT_EQ(-1, event2.FindPointerIndexOfId(1));
+  EXPECT_EQ(-1, event2.FindPointerIndexOfId(2));
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/scale_gesture_detector.cc b/ui/events/gesture_detection/scale_gesture_detector.cc
new file mode 100644
index 0000000..7afe91f
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_detector.cc
@@ -0,0 +1,354 @@
+// Copyright 2014 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 "ui/events/gesture_detection/scale_gesture_detector.h"
+
+#include <limits.h>
+#include <cmath>
+
+#include "base/float_util.h"
+#include "base/logging.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/events/gesture_detection/scale_gesture_listeners.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace ui {
+namespace {
+
+// Using a small epsilon when comparing slop distances allows pixel perfect
+// slop determination when using fractional DPI coordinates (assuming the slop
+// region and DPI scale are reasonably proportioned).
+const float kSlopEpsilon = .05f;
+
+const int kTouchStabilizeTimeMs = 128;
+
+const float kScaleFactor = .5f;
+
+}  // namespace
+
+// Note: These constants were taken directly from the default (unscaled)
+// versions found in Android's ViewConfiguration.
+ScaleGestureDetector::Config::Config()
+    : span_slop(16),
+      min_scaling_touch_major(48),
+      min_scaling_span(200),
+      min_pinch_update_span_delta(0) {
+}
+
+ScaleGestureDetector::Config::~Config() {}
+
+ScaleGestureDetector::ScaleGestureDetector(const Config& config,
+                                           ScaleGestureListener* listener)
+    : listener_(listener),
+      focus_x_(0),
+      focus_y_(0),
+      curr_span_(0),
+      prev_span_(0),
+      initial_span_(0),
+      curr_span_x_(0),
+      curr_span_y_(0),
+      prev_span_x_(0),
+      prev_span_y_(0),
+      in_progress_(0),
+      span_slop_(0),
+      min_span_(0),
+      touch_upper_(0),
+      touch_lower_(0),
+      touch_history_last_accepted_(0),
+      touch_history_direction_(0),
+      touch_min_major_(0),
+      touch_max_major_(0),
+      double_tap_focus_x_(0),
+      double_tap_focus_y_(0),
+      double_tap_mode_(DOUBLE_TAP_MODE_NONE),
+      event_before_or_above_starting_gesture_event_(false) {
+  DCHECK(listener_);
+  span_slop_ = config.span_slop + kSlopEpsilon;
+  touch_min_major_ = config.min_scaling_touch_major;
+  touch_max_major_ = std::min(config.min_scaling_span / std::sqrt(2.f),
+                              2.f * touch_min_major_);
+  min_span_ = config.min_scaling_span + kSlopEpsilon;
+  ResetTouchHistory();
+}
+
+ScaleGestureDetector::~ScaleGestureDetector() {}
+
+bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
+  curr_time_ = event.GetEventTime();
+
+  const int action = event.GetAction();
+
+  const bool stream_complete =
+      action == MotionEvent::ACTION_UP ||
+      action == MotionEvent::ACTION_CANCEL ||
+      (action == MotionEvent::ACTION_POINTER_DOWN && InDoubleTapMode());
+
+  if (action == MotionEvent::ACTION_DOWN || stream_complete) {
+    // Reset any scale in progress with the listener.
+    // If it's an ACTION_DOWN we're beginning a new event stream.
+    // This means the app probably didn't give us all the events. Shame on it.
+    if (in_progress_) {
+      listener_->OnScaleEnd(*this, event);
+      ResetScaleWithSpan(0);
+    } else if (InDoubleTapMode() && stream_complete) {
+      ResetScaleWithSpan(0);
+    }
+
+    if (stream_complete) {
+      ResetTouchHistory();
+      return true;
+    }
+  }
+
+  const bool config_changed = action == MotionEvent::ACTION_DOWN ||
+                              action == MotionEvent::ACTION_POINTER_UP ||
+                              action == MotionEvent::ACTION_POINTER_DOWN;
+
+  const bool pointer_up = action == MotionEvent::ACTION_POINTER_UP;
+  const int skip_index = pointer_up ? event.GetActionIndex() : -1;
+
+  // Determine focal point.
+  float sum_x = 0, sum_y = 0;
+  const int count = static_cast<int>(event.GetPointerCount());
+  const int unreleased_point_count = pointer_up ? count - 1 : count;
+  const float inverse_unreleased_point_count = 1.0f / unreleased_point_count;
+
+  float focus_x;
+  float focus_y;
+  if (InDoubleTapMode()) {
+    // In double tap mode, the focal pt is always where the double tap
+    // gesture started.
+    focus_x = double_tap_focus_x_;
+    focus_y = double_tap_focus_y_;
+    if (event.GetY() < focus_y) {
+      event_before_or_above_starting_gesture_event_ = true;
+    } else {
+      event_before_or_above_starting_gesture_event_ = false;
+    }
+  } else {
+    for (int i = 0; i < count; i++) {
+      if (skip_index == i)
+        continue;
+      sum_x += event.GetX(i);
+      sum_y += event.GetY(i);
+    }
+
+    focus_x = sum_x * inverse_unreleased_point_count;
+    focus_y = sum_y * inverse_unreleased_point_count;
+  }
+
+  AddTouchHistory(event);
+
+  // Determine average deviation from focal point.
+  float dev_sum_x = 0, dev_sum_y = 0;
+  for (int i = 0; i < count; i++) {
+    if (skip_index == i)
+      continue;
+
+    dev_sum_x += std::abs(event.GetX(i) - focus_x);
+    dev_sum_y += std::abs(event.GetY(i) - focus_y);
+  }
+  // Convert the resulting diameter into a radius, to include touch
+  // radius in overall deviation.
+  const float touch_radius = touch_history_last_accepted_ / 2;
+
+  const float dev_x = dev_sum_x * inverse_unreleased_point_count + touch_radius;
+  const float dev_y = dev_sum_y * inverse_unreleased_point_count + touch_radius;
+
+  // Span is the average distance between touch points through the focal point;
+  // i.e. the diameter of the circle with a radius of the average deviation from
+  // the focal point.
+  const float span_x = dev_x * 2;
+  const float span_y = dev_y * 2;
+  float span;
+  if (InDoubleTapMode()) {
+    span = span_y;
+  } else {
+    span = std::sqrt(span_x * span_x + span_y * span_y);
+  }
+
+  // Dispatch begin/end events as needed.
+  // If the configuration changes, notify the app to reset its current state by
+  // beginning a fresh scale event stream.
+  const bool was_in_progress = in_progress_;
+  focus_x_ = focus_x;
+  focus_y_ = focus_y;
+  if (!InDoubleTapMode() && in_progress_ &&
+      (span < min_span_ || config_changed)) {
+    listener_->OnScaleEnd(*this, event);
+    ResetScaleWithSpan(span);
+  }
+  if (config_changed) {
+    prev_span_x_ = curr_span_x_ = span_x;
+    prev_span_y_ = curr_span_y_ = span_y;
+    initial_span_ = prev_span_ = curr_span_ = span;
+  }
+
+  const float min_span = InDoubleTapMode() ? span_slop_ : min_span_;
+  if (!in_progress_ && span >= min_span &&
+      (was_in_progress || std::abs(span - initial_span_) > span_slop_)) {
+    prev_span_x_ = curr_span_x_ = span_x;
+    prev_span_y_ = curr_span_y_ = span_y;
+    prev_span_ = curr_span_ = span;
+    prev_time_ = curr_time_;
+    in_progress_ = listener_->OnScaleBegin(*this, event);
+  }
+
+  // Handle motion; focal point and span/scale factor are changing.
+  if (action == MotionEvent::ACTION_MOVE) {
+    curr_span_x_ = span_x;
+    curr_span_y_ = span_y;
+    curr_span_ = span;
+
+    bool update_prev = true;
+
+    if (in_progress_) {
+      update_prev = listener_->OnScale(*this, event);
+    }
+
+    if (update_prev) {
+      prev_span_x_ = curr_span_x_;
+      prev_span_y_ = curr_span_y_;
+      prev_span_ = curr_span_;
+      prev_time_ = curr_time_;
+    }
+  }
+
+  return true;
+}
+
+bool ScaleGestureDetector::IsInProgress() const { return in_progress_; }
+
+bool ScaleGestureDetector::InDoubleTapMode() const {
+  return double_tap_mode_ == DOUBLE_TAP_MODE_IN_PROGRESS;
+}
+
+float ScaleGestureDetector::GetFocusX() const { return focus_x_; }
+
+float ScaleGestureDetector::GetFocusY() const { return focus_y_; }
+
+float ScaleGestureDetector::GetCurrentSpan() const { return curr_span_; }
+
+float ScaleGestureDetector::GetCurrentSpanX() const { return curr_span_x_; }
+
+float ScaleGestureDetector::GetCurrentSpanY() const { return curr_span_y_; }
+
+float ScaleGestureDetector::GetPreviousSpan() const { return prev_span_; }
+
+float ScaleGestureDetector::GetPreviousSpanX() const { return prev_span_x_; }
+
+float ScaleGestureDetector::GetPreviousSpanY() const { return prev_span_y_; }
+
+float ScaleGestureDetector::GetScaleFactor() const {
+  if (InDoubleTapMode()) {
+    // Drag is moving up; the further away from the gesture start, the smaller
+    // the span should be, the closer, the larger the span, and therefore the
+    // larger the scale.
+    const bool scale_up = (event_before_or_above_starting_gesture_event_ &&
+                           (curr_span_ < prev_span_)) ||
+                          (!event_before_or_above_starting_gesture_event_ &&
+                           (curr_span_ > prev_span_));
+    const float span_diff =
+        (std::abs(1.f - (curr_span_ / prev_span_)) * kScaleFactor);
+    return prev_span_ <= 0 ? 1.f
+                           : (scale_up ? (1.f + span_diff) : (1.f - span_diff));
+  }
+  return prev_span_ > 0 ? curr_span_ / prev_span_ : 1;
+}
+
+base::TimeDelta ScaleGestureDetector::GetTimeDelta() const {
+  return curr_time_ - prev_time_;
+}
+
+base::TimeTicks ScaleGestureDetector::GetEventTime() const {
+  return curr_time_;
+}
+
+bool ScaleGestureDetector::OnDoubleTap(const MotionEvent& ev) {
+  // Double tap: start watching for a swipe.
+  double_tap_focus_x_ = ev.GetX();
+  double_tap_focus_y_ = ev.GetY();
+  double_tap_mode_ = DOUBLE_TAP_MODE_IN_PROGRESS;
+  return true;
+}
+
+void ScaleGestureDetector::AddTouchHistory(const MotionEvent& ev) {
+  const base::TimeTicks current_time = ev.GetEventTime();
+  DCHECK(!current_time.is_null());
+  const int count = static_cast<int>(ev.GetPointerCount());
+  bool accept = touch_history_last_accepted_time_.is_null() ||
+                (current_time - touch_history_last_accepted_time_) >=
+                    base::TimeDelta::FromMilliseconds(kTouchStabilizeTimeMs);
+  float total = 0;
+  int sample_count = 0;
+  for (int i = 0; i < count; i++) {
+    const bool has_last_accepted = !base::IsNaN(touch_history_last_accepted_);
+    const int history_size = static_cast<int>(ev.GetHistorySize());
+    const int pointersample_count = history_size + 1;
+    for (int h = 0; h < pointersample_count; h++) {
+      float major;
+      if (h < history_size) {
+        major = ev.GetHistoricalTouchMajor(i, h);
+      } else {
+        major = ev.GetTouchMajor(i);
+      }
+      if (major < touch_min_major_)
+        major = touch_min_major_;
+      if (major > touch_max_major_)
+        major = touch_max_major_;
+      total += major;
+
+      if (base::IsNaN(touch_upper_) || major > touch_upper_) {
+        touch_upper_ = major;
+      }
+      if (base::IsNaN(touch_lower_) || major < touch_lower_) {
+        touch_lower_ = major;
+      }
+
+      if (has_last_accepted) {
+        const float major_delta = major - touch_history_last_accepted_;
+        const int direction_sig =
+            major_delta > 0 ? 1 : (major_delta < 0 ? -1 : 0);
+        if (direction_sig != touch_history_direction_ ||
+            (direction_sig == 0 && touch_history_direction_ == 0)) {
+          touch_history_direction_ = direction_sig;
+          touch_history_last_accepted_time_ = h < history_size
+                                                  ? ev.GetHistoricalEventTime(h)
+                                                  : ev.GetEventTime();
+          accept = false;
+        }
+      }
+    }
+    sample_count += pointersample_count;
+  }
+
+  const float avg = total / sample_count;
+
+  if (accept) {
+    float new_accepted = (touch_upper_ + touch_lower_ + avg) / 3;
+    touch_upper_ = (touch_upper_ + new_accepted) / 2;
+    touch_lower_ = (touch_lower_ + new_accepted) / 2;
+    touch_history_last_accepted_ = new_accepted;
+    touch_history_direction_ = 0;
+    touch_history_last_accepted_time_ = ev.GetEventTime();
+  }
+}
+
+void ScaleGestureDetector::ResetTouchHistory() {
+  touch_upper_ = std::numeric_limits<float>::quiet_NaN();
+  touch_lower_ = std::numeric_limits<float>::quiet_NaN();
+  touch_history_last_accepted_ = std::numeric_limits<float>::quiet_NaN();
+  touch_history_direction_ = 0;
+  touch_history_last_accepted_time_ = base::TimeTicks();
+}
+
+void ScaleGestureDetector::ResetScaleWithSpan(float span) {
+  in_progress_ = false;
+  initial_span_ = span;
+  double_tap_mode_ = DOUBLE_TAP_MODE_NONE;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/scale_gesture_detector.h b/ui/events/gesture_detection/scale_gesture_detector.h
new file mode 100644
index 0000000..d45ca4e
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_detector.h
@@ -0,0 +1,125 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
+#define UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
+
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+class ScaleGestureListener;
+
+// Port of ScaleGestureDetector.java from Android
+// * platform/frameworks/base/core/java/android/view/ScaleGestureDetector.java
+// * Change-Id: I3e7926a4f6f9ab4951f380bd004499c78b3bda69
+// * Please update the Change-Id as upstream Android changes are pulled.
+class GESTURE_DETECTION_EXPORT ScaleGestureDetector {
+ public:
+  struct GESTURE_DETECTION_EXPORT Config {
+    Config();
+    ~Config();
+
+    // Distance the current span can deviate from the initial span before
+    // scaling will start (in dips). The span is the diameter of the circle with
+    // a radius of average pointer deviation from the focal point.
+    float span_slop;
+
+    // Minimum accepted value for TouchMajor while scaling (in dips).
+    float min_scaling_touch_major;
+
+    // Minimum span needed to initiate a scaling gesture (in dips).
+    float min_scaling_span;
+
+    // Minimum pinch span change before pinch occurs (in dips). See
+    // crbug.com/373318.
+    float min_pinch_update_span_delta;
+  };
+
+  ScaleGestureDetector(const Config& config, ScaleGestureListener* listener);
+  virtual ~ScaleGestureDetector();
+
+  // Accepts MotionEvents and dispatches events to a |ScaleGestureListener|
+  // when appropriate.
+  //
+  // Note: Applications should pass a complete and consistent event stream to
+  // this method. A complete and consistent event stream involves all
+  // MotionEvents from the initial ACTION_DOWN to the final ACTION_UP or
+  // ACTION_CANCEL.
+  //
+  // Returns true if the event was processed and the detector wants to receive
+  // the rest of the MotionEvents in this event stream.
+  bool OnTouchEvent(const MotionEvent& event);
+
+  // This method may be called by the owner when a a double-tap event has been
+  // detected *for the same event stream* being fed to this instance of the
+  // ScaleGestureDetector. As call order is important here, the double-tap
+  // detector should always be offered events *before* the ScaleGestureDetector.
+  bool OnDoubleTap(const MotionEvent& event);
+
+  // Set whether the associated |ScaleGestureListener| should receive
+  // OnScale callbacks when the user performs a doubletap followed by a swipe.
+  bool IsInProgress() const;
+  bool InDoubleTapMode() const;
+  float GetFocusX() const;
+  float GetFocusY() const;
+  float GetCurrentSpan() const;
+  float GetCurrentSpanX() const;
+  float GetCurrentSpanY() const;
+  float GetPreviousSpan() const;
+  float GetPreviousSpanX() const;
+  float GetPreviousSpanY() const;
+  float GetScaleFactor() const;
+  base::TimeDelta GetTimeDelta() const;
+  base::TimeTicks GetEventTime() const;
+
+ private:
+  enum DoubleTapMode { DOUBLE_TAP_MODE_NONE, DOUBLE_TAP_MODE_IN_PROGRESS };
+
+  // The TouchMajor/TouchMinor elements of a MotionEvent can flutter/jitter on
+  // some hardware/driver combos. Smooth out to get kinder, gentler behavior.
+  void AddTouchHistory(const MotionEvent& ev);
+  void ResetTouchHistory();
+
+  void ResetScaleWithSpan(float span);
+
+  ScaleGestureListener* const listener_;
+
+  float focus_x_;
+  float focus_y_;
+  float curr_span_;
+  float prev_span_;
+  float initial_span_;
+  float curr_span_x_;
+  float curr_span_y_;
+  float prev_span_x_;
+  float prev_span_y_;
+  base::TimeTicks curr_time_;
+  base::TimeTicks prev_time_;
+  bool in_progress_;
+  float span_slop_;
+  float min_span_;
+
+  // Bounds for recently seen values.
+  float touch_upper_;
+  float touch_lower_;
+  float touch_history_last_accepted_;
+  int touch_history_direction_;
+  base::TimeTicks touch_history_last_accepted_time_;
+  float touch_min_major_;
+  float touch_max_major_;
+  float double_tap_focus_x_;
+  float double_tap_focus_y_;
+  DoubleTapMode double_tap_mode_;
+
+  bool event_before_or_above_starting_gesture_event_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScaleGestureDetector);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
diff --git a/ui/events/gesture_detection/scale_gesture_listeners.cc b/ui/events/gesture_detection/scale_gesture_listeners.cc
new file mode 100644
index 0000000..98c957a
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_listeners.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 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 "ui/events/gesture_detection/scale_gesture_listeners.h"
+
+namespace ui {
+
+bool SimpleScaleGestureListener::OnScale(const ScaleGestureDetector&,
+                                         const MotionEvent&) {
+  return false;
+}
+
+bool SimpleScaleGestureListener::OnScaleBegin(const ScaleGestureDetector&,
+                                              const MotionEvent&) {
+  return true;
+}
+
+void SimpleScaleGestureListener::OnScaleEnd(const ScaleGestureDetector&,
+                                            const MotionEvent&) {
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/scale_gesture_listeners.h b/ui/events/gesture_detection/scale_gesture_listeners.h
new file mode 100644
index 0000000..583ef7e
--- /dev/null
+++ b/ui/events/gesture_detection/scale_gesture_listeners.h
@@ -0,0 +1,47 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_LISTENERS_H_
+#define UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_LISTENERS_H_
+
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace ui {
+
+class MotionEvent;
+class ScaleGestureDetector;
+
+// Client through which |ScaleGestureDetector| signals scale detection.
+class GESTURE_DETECTION_EXPORT ScaleGestureListener {
+ public:
+  virtual ~ScaleGestureListener() {}
+  virtual bool OnScale(const ScaleGestureDetector& detector,
+                       const MotionEvent& e) = 0;
+  virtual bool OnScaleBegin(const ScaleGestureDetector& detector,
+                            const MotionEvent& e) = 0;
+  virtual void OnScaleEnd(const ScaleGestureDetector& detector,
+                          const MotionEvent& e) = 0;
+};
+
+// A convenience class to extend when you only want to listen for a subset of
+// scaling-related events. This implements all methods in
+// |ScaleGestureListener| but does nothing.
+// |OnScale()| returns false so that a subclass can retrieve the accumulated
+// scale factor in an overridden |OnScaleEnd()|.
+// |OnScaleBegin() returns true.
+class GESTURE_DETECTION_EXPORT SimpleScaleGestureListener
+    : public ScaleGestureListener {
+ public:
+  // ScaleGestureListener implementation.
+  virtual bool OnScale(const ScaleGestureDetector&,
+                       const MotionEvent&) override;
+  virtual bool OnScaleBegin(const ScaleGestureDetector&,
+                            const MotionEvent&) override;
+  virtual void OnScaleEnd(const ScaleGestureDetector&,
+                          const MotionEvent&) override;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_LISTENERS_H_
diff --git a/ui/events/gesture_detection/snap_scroll_controller.cc b/ui/events/gesture_detection/snap_scroll_controller.cc
new file mode 100644
index 0000000..bde5a35
--- /dev/null
+++ b/ui/events/gesture_detection/snap_scroll_controller.cc
@@ -0,0 +1,106 @@
+// Copyright 2014 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 "ui/events/gesture_detection/snap_scroll_controller.h"
+
+#include <cmath>
+
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/gfx/display.h"
+
+namespace ui {
+namespace {
+const int kSnapBound = 16;
+const float kMinSnapChannelDistance = kSnapBound;
+const float kMaxSnapChannelDistance = kMinSnapChannelDistance * 3.f;
+const float kSnapChannelDipsPerScreenDip = kMinSnapChannelDistance / 480.f;
+
+float CalculateChannelDistance(const gfx::Display& display) {
+  if (display.bounds().IsEmpty())
+    return kMinSnapChannelDistance;
+
+  float screen_size =
+      std::abs(hypot(static_cast<float>(display.bounds().width()),
+                     static_cast<float>(display.bounds().height())));
+
+  float snap_channel_distance = screen_size * kSnapChannelDipsPerScreenDip;
+  return std::max(kMinSnapChannelDistance,
+                  std::min(kMaxSnapChannelDistance, snap_channel_distance));
+}
+
+}  // namespace
+
+
+SnapScrollController::SnapScrollController(const gfx::Display& display)
+    : channel_distance_(CalculateChannelDistance(display)),
+      snap_scroll_mode_(SNAP_NONE),
+      first_touch_x_(-1),
+      first_touch_y_(-1),
+      distance_x_(0),
+      distance_y_(0) {}
+
+SnapScrollController::~SnapScrollController() {}
+
+void SnapScrollController::UpdateSnapScrollMode(float distance_x,
+                                                float distance_y) {
+  if (snap_scroll_mode_ == SNAP_HORIZ || snap_scroll_mode_ == SNAP_VERT) {
+    distance_x_ += std::abs(distance_x);
+    distance_y_ += std::abs(distance_y);
+    if (snap_scroll_mode_ == SNAP_HORIZ) {
+      if (distance_y_ > channel_distance_) {
+        snap_scroll_mode_ = SNAP_NONE;
+      } else if (distance_x_ > channel_distance_) {
+        distance_x_ = 0;
+        distance_y_ = 0;
+      }
+    } else {
+      if (distance_x_ > channel_distance_) {
+        snap_scroll_mode_ = SNAP_NONE;
+      } else if (distance_y_ > channel_distance_) {
+        distance_x_ = 0;
+        distance_y_ = 0;
+      }
+    }
+  }
+}
+
+void SnapScrollController::SetSnapScrollingMode(
+    const MotionEvent& event,
+    bool is_scale_gesture_detection_in_progress) {
+  switch (event.GetAction()) {
+    case MotionEvent::ACTION_DOWN:
+      snap_scroll_mode_ = SNAP_NONE;
+      first_touch_x_ = event.GetX();
+      first_touch_y_ = event.GetY();
+      break;
+    // Set scrolling mode to SNAP_X if scroll towards x-axis exceeds kSnapBound
+    // and movement towards y-axis is trivial.
+    // Set scrolling mode to SNAP_Y if scroll towards y-axis exceeds kSnapBound
+    // and movement towards x-axis is trivial.
+    // Scrolling mode will remain in SNAP_NONE for other conditions.
+    case MotionEvent::ACTION_MOVE:
+      if (!is_scale_gesture_detection_in_progress &&
+          snap_scroll_mode_ == SNAP_NONE) {
+        int x_diff = static_cast<int>(std::abs(event.GetX() - first_touch_x_));
+        int y_diff = static_cast<int>(std::abs(event.GetY() - first_touch_y_));
+        if (x_diff > kSnapBound && y_diff < kSnapBound) {
+          snap_scroll_mode_ = SNAP_HORIZ;
+        } else if (x_diff < kSnapBound && y_diff > kSnapBound) {
+          snap_scroll_mode_ = SNAP_VERT;
+        }
+      }
+      break;
+    case MotionEvent::ACTION_UP:
+    case MotionEvent::ACTION_CANCEL:
+      first_touch_x_ = -1;
+      first_touch_y_ = -1;
+      distance_x_ = 0;
+      distance_y_ = 0;
+      break;
+    default:
+      break;
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/snap_scroll_controller.h b/ui/events/gesture_detection/snap_scroll_controller.h
new file mode 100644
index 0000000..753c2bc
--- /dev/null
+++ b/ui/events/gesture_detection/snap_scroll_controller.h
@@ -0,0 +1,60 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_SNAP_SCROLL_CONTROLLER_H_
+#define UI_EVENTS_GESTURE_DETECTION_SNAP_SCROLL_CONTROLLER_H_
+
+#include "base/basictypes.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+
+namespace gfx {
+class Display;
+}
+
+namespace ui {
+
+class MotionEvent;
+class ZoomManager;
+
+// Port of SnapScrollController.java from Chromium
+// Controls the scroll snapping behavior based on scroll updates.
+class SnapScrollController {
+ public:
+  explicit SnapScrollController(const gfx::Display& display);
+  ~SnapScrollController();
+
+  // Updates the snap scroll mode based on the given X and Y distance to be
+  // moved on scroll.  If the scroll update is above a threshold, the snapping
+  // behavior is reset.
+  void UpdateSnapScrollMode(float distance_x, float distance_y);
+
+  // Sets the snap scroll mode based on the event type.
+  void SetSnapScrollingMode(const MotionEvent& event,
+                            bool is_scale_gesture_detection_in_progress);
+
+  void ResetSnapScrollMode() { snap_scroll_mode_ = SNAP_NONE; }
+  bool IsSnapVertical() const { return snap_scroll_mode_ == SNAP_VERT; }
+  bool IsSnapHorizontal() const { return snap_scroll_mode_ == SNAP_HORIZ; }
+  bool IsSnappingScrolls() const { return snap_scroll_mode_ != SNAP_NONE; }
+
+ private:
+  enum SnapMode {
+    SNAP_NONE,
+    SNAP_HORIZ,
+    SNAP_VERT
+  };
+
+  float channel_distance_;
+  SnapMode snap_scroll_mode_;
+  float first_touch_x_;
+  float first_touch_y_;
+  float distance_x_;
+  float distance_y_;
+
+  DISALLOW_COPY_AND_ASSIGN(SnapScrollController);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_SNAP_SCROLL_CONTROLLER_H_
diff --git a/ui/events/gesture_detection/touch_disposition_gesture_filter.cc b/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
new file mode 100644
index 0000000..fcb92d4
--- /dev/null
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
@@ -0,0 +1,427 @@
+// Copyright 2014 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 "ui/events/gesture_detection/touch_disposition_gesture_filter.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "ui/events/gesture_event_details.h"
+
+namespace ui {
+namespace {
+
+// A BitSet32 is used for tracking dropped gesture types.
+COMPILE_ASSERT(ET_GESTURE_TYPE_END - ET_GESTURE_TYPE_START < 32,
+               gesture_type_count_too_large);
+
+GestureEventData CreateGesture(EventType type,
+                               int motion_event_id,
+                               MotionEvent::ToolType primary_tool_type,
+                               const GestureEventDataPacket& packet) {
+  // As the event is purely synthetic, we needn't be strict with event flags.
+  int flags = EF_NONE;
+  return GestureEventData(GestureEventDetails(type),
+                          motion_event_id,
+                          primary_tool_type,
+                          packet.timestamp(),
+                          packet.touch_location().x(),
+                          packet.touch_location().y(),
+                          packet.raw_touch_location().x(),
+                          packet.raw_touch_location().y(),
+                          1,
+                          gfx::RectF(packet.touch_location(), gfx::SizeF()),
+                          flags);
+}
+
+enum RequiredTouches {
+  RT_NONE = 0,
+  RT_START = 1 << 0,
+  RT_CURRENT = 1 << 1,
+};
+
+struct DispositionHandlingInfo {
+  // A bitwise-OR of |RequiredTouches|.
+  int required_touches;
+  EventType antecedent_event_type;
+
+  explicit DispositionHandlingInfo(int required_touches)
+      : required_touches(required_touches), antecedent_event_type(ET_UNKNOWN) {}
+
+  DispositionHandlingInfo(int required_touches,
+                          EventType antecedent_event_type)
+      : required_touches(required_touches),
+        antecedent_event_type(antecedent_event_type) {}
+};
+
+DispositionHandlingInfo Info(int required_touches) {
+  return DispositionHandlingInfo(required_touches);
+}
+
+DispositionHandlingInfo Info(int required_touches,
+                             EventType antecedent_event_type) {
+  return DispositionHandlingInfo(required_touches, antecedent_event_type);
+}
+
+// This approach to disposition handling is described at http://goo.gl/5G8PWJ.
+DispositionHandlingInfo GetDispositionHandlingInfo(EventType type) {
+  switch (type) {
+    case ET_GESTURE_TAP_DOWN:
+      return Info(RT_START);
+    case ET_GESTURE_TAP_CANCEL:
+      return Info(RT_START);
+    case ET_GESTURE_SHOW_PRESS:
+      return Info(RT_START);
+    case ET_GESTURE_LONG_PRESS:
+      return Info(RT_START);
+    case ET_GESTURE_LONG_TAP:
+      return Info(RT_START | RT_CURRENT);
+    case ET_GESTURE_TAP:
+      return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
+    case ET_GESTURE_TAP_UNCONFIRMED:
+      return Info(RT_START | RT_CURRENT);
+    case ET_GESTURE_DOUBLE_TAP:
+      return Info(RT_START | RT_CURRENT, ET_GESTURE_TAP_UNCONFIRMED);
+    case ET_GESTURE_SCROLL_BEGIN:
+      return Info(RT_START);
+    case ET_GESTURE_SCROLL_UPDATE:
+      return Info(RT_CURRENT, ET_GESTURE_SCROLL_BEGIN);
+    case ET_GESTURE_SCROLL_END:
+      return Info(RT_NONE, ET_GESTURE_SCROLL_BEGIN);
+    case ET_SCROLL_FLING_START:
+      // We rely on |EndScrollGestureIfNecessary| to end the scroll if the fling
+      // start is prevented.
+      return Info(RT_NONE, ET_GESTURE_SCROLL_UPDATE);
+    case ET_SCROLL_FLING_CANCEL:
+      return Info(RT_NONE, ET_SCROLL_FLING_START);
+    case ET_GESTURE_PINCH_BEGIN:
+      return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
+    case ET_GESTURE_PINCH_UPDATE:
+      return Info(RT_CURRENT, ET_GESTURE_PINCH_BEGIN);
+    case ET_GESTURE_PINCH_END:
+      return Info(RT_NONE, ET_GESTURE_PINCH_BEGIN);
+    case ET_GESTURE_BEGIN:
+      return Info(RT_START);
+    case ET_GESTURE_END:
+      return Info(RT_NONE, ET_GESTURE_BEGIN);
+    case ET_GESTURE_SWIPE:
+      return Info(RT_START, ET_GESTURE_SCROLL_BEGIN);
+    case ET_GESTURE_TWO_FINGER_TAP:
+      return Info(RT_START);
+    default:
+      break;
+  }
+  NOTREACHED();
+  return Info(RT_NONE);
+}
+
+int GetGestureTypeIndex(EventType type) {
+  DCHECK_GE(type, ET_GESTURE_TYPE_START);
+  DCHECK_LE(type, ET_GESTURE_TYPE_END);
+  return type - ET_GESTURE_TYPE_START;
+}
+
+bool IsTouchStartEvent(GestureEventDataPacket::GestureSource gesture_source) {
+  return gesture_source == GestureEventDataPacket::TOUCH_SEQUENCE_START ||
+         gesture_source == GestureEventDataPacket::TOUCH_START;
+}
+
+}  // namespace
+
+// TouchDispositionGestureFilter
+
+TouchDispositionGestureFilter::TouchDispositionGestureFilter(
+    TouchDispositionGestureFilterClient* client)
+    : client_(client),
+      ending_event_motion_event_id_(0),
+      ending_event_primary_tool_type_(MotionEvent::TOOL_TYPE_UNKNOWN),
+      needs_tap_ending_event_(false),
+      needs_show_press_event_(false),
+      needs_fling_ending_event_(false),
+      needs_scroll_ending_event_(false) {
+  DCHECK(client_);
+}
+
+TouchDispositionGestureFilter::~TouchDispositionGestureFilter() {
+}
+
+TouchDispositionGestureFilter::PacketResult
+TouchDispositionGestureFilter::OnGesturePacket(
+    const GestureEventDataPacket& packet) {
+  if (packet.gesture_source() == GestureEventDataPacket::UNDEFINED ||
+      packet.gesture_source() == GestureEventDataPacket::INVALID)
+    return INVALID_PACKET_TYPE;
+
+  if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START)
+    sequences_.push(GestureSequence());
+
+  if (IsEmpty())
+    return INVALID_PACKET_ORDER;
+
+  if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT &&
+      Tail().empty()) {
+    // Handle the timeout packet immediately if the packet preceding the timeout
+    // has already been dispatched.
+    FilterAndSendPacket(packet);
+    return SUCCESS;
+  }
+
+  Tail().push(packet);
+  return SUCCESS;
+}
+
+void TouchDispositionGestureFilter::OnTouchEventAck(bool event_consumed) {
+  // Spurious touch acks from the renderer should not trigger a crash.
+  if (IsEmpty() || (Head().empty() && sequences_.size() == 1))
+    return;
+
+  if (Head().empty())
+    PopGestureSequence();
+
+  GestureSequence& sequence = Head();
+
+  // Dispatch the packet corresponding to the ack'ed touch, as well as any
+  // additional timeout-based packets queued before the ack was received.
+  bool touch_packet_for_current_ack_handled = false;
+  while (!sequence.empty()) {
+    DCHECK_NE(sequence.front().gesture_source(),
+              GestureEventDataPacket::UNDEFINED);
+    DCHECK_NE(sequence.front().gesture_source(),
+              GestureEventDataPacket::INVALID);
+
+    GestureEventDataPacket::GestureSource source =
+        sequence.front().gesture_source();
+    if (source != GestureEventDataPacket::TOUCH_TIMEOUT) {
+      // We should handle at most one non-timeout based packet.
+      if (touch_packet_for_current_ack_handled)
+        break;
+      state_.OnTouchEventAck(event_consumed, IsTouchStartEvent(source));
+      touch_packet_for_current_ack_handled = true;
+    }
+    // We need to pop the current sequence before sending the packet, because
+    // sending the packet could result in this method being re-entered (e.g. on
+    // Aura, we could trigger a touch-cancel). As popping the sequence destroys
+    // the packet, we copy the packet before popping it.
+    const GestureEventDataPacket packet = sequence.front();
+    sequence.pop();
+    FilterAndSendPacket(packet);
+  }
+  DCHECK(touch_packet_for_current_ack_handled);
+}
+
+bool TouchDispositionGestureFilter::IsEmpty() const {
+  return sequences_.empty();
+}
+
+void TouchDispositionGestureFilter::FilterAndSendPacket(
+    const GestureEventDataPacket& packet) {
+  if (packet.gesture_source() == GestureEventDataPacket::TOUCH_SEQUENCE_START) {
+    CancelTapIfNecessary(packet);
+    EndScrollIfNecessary(packet);
+    CancelFlingIfNecessary(packet);
+  } else if (packet.gesture_source() == GestureEventDataPacket::TOUCH_START) {
+    CancelTapIfNecessary(packet);
+  }
+  int gesture_end_index = -1;
+  for (size_t i = 0; i < packet.gesture_count(); ++i) {
+    const GestureEventData& gesture = packet.gesture(i);
+    DCHECK_GE(gesture.details.type(), ET_GESTURE_TYPE_START);
+    DCHECK_LE(gesture.details.type(), ET_GESTURE_TYPE_END);
+    if (state_.Filter(gesture.details.type())) {
+      CancelTapIfNecessary(packet);
+      continue;
+    }
+    if (packet.gesture_source() == GestureEventDataPacket::TOUCH_TIMEOUT) {
+      // Sending a timed gesture could delete |this|, so we need to return
+      // directly after the |SendGesture| call.
+      SendGesture(gesture, packet);
+      // We should not have a timeout gesture and other gestures in the same
+      // packet.
+      DCHECK_EQ(1U, packet.gesture_count());
+      return;
+    }
+    // Occasionally scroll or tap cancel events are synthesized when a touch
+    // sequence has been canceled or terminated, we want to make sure that
+    // ET_GESTURE_END always happens after them.
+    if (gesture.type() == ET_GESTURE_END) {
+      // Make sure there is at most one ET_GESTURE_END event in each packet.
+      DCHECK_EQ(-1, gesture_end_index);
+      gesture_end_index = static_cast<int>(i);
+      continue;
+    }
+    SendGesture(gesture, packet);
+  }
+
+  if (packet.gesture_source() ==
+      GestureEventDataPacket::TOUCH_SEQUENCE_CANCEL) {
+    EndScrollIfNecessary(packet);
+    CancelTapIfNecessary(packet);
+  } else if (packet.gesture_source() ==
+             GestureEventDataPacket::TOUCH_SEQUENCE_END) {
+    EndScrollIfNecessary(packet);
+  }
+  // Always send the ET_GESTURE_END event as the last one for every touch event.
+  if (gesture_end_index >= 0)
+    SendGesture(packet.gesture(gesture_end_index), packet);
+}
+
+void TouchDispositionGestureFilter::SendGesture(
+    const GestureEventData& event,
+    const GestureEventDataPacket& packet_being_sent) {
+  // TODO(jdduke): Factor out gesture stream reparation code into a standalone
+  // utility class.
+  switch (event.type()) {
+    case ET_GESTURE_LONG_TAP:
+      if (!needs_tap_ending_event_)
+        return;
+      CancelTapIfNecessary(packet_being_sent);
+      CancelFlingIfNecessary(packet_being_sent);
+      break;
+    case ET_GESTURE_TAP_DOWN:
+      DCHECK(!needs_tap_ending_event_);
+      ending_event_motion_event_id_ = event.motion_event_id;
+      ending_event_primary_tool_type_ = event.primary_tool_type;
+      needs_show_press_event_ = true;
+      needs_tap_ending_event_ = true;
+      break;
+    case ET_GESTURE_SHOW_PRESS:
+      if (!needs_show_press_event_)
+        return;
+      needs_show_press_event_ = false;
+      break;
+    case ET_GESTURE_DOUBLE_TAP:
+      CancelTapIfNecessary(packet_being_sent);
+      needs_show_press_event_ = false;
+      break;
+    case ET_GESTURE_TAP:
+      DCHECK(needs_tap_ending_event_);
+      if (needs_show_press_event_) {
+        SendGesture(GestureEventData(ET_GESTURE_SHOW_PRESS, event),
+                    packet_being_sent);
+        DCHECK(!needs_show_press_event_);
+      }
+      needs_tap_ending_event_ = false;
+      break;
+    case ET_GESTURE_TAP_CANCEL:
+      needs_show_press_event_ = false;
+      needs_tap_ending_event_ = false;
+      break;
+    case ET_GESTURE_SCROLL_BEGIN:
+      CancelTapIfNecessary(packet_being_sent);
+      CancelFlingIfNecessary(packet_being_sent);
+      EndScrollIfNecessary(packet_being_sent);
+      ending_event_motion_event_id_ = event.motion_event_id;
+      ending_event_primary_tool_type_ = event.primary_tool_type;
+      needs_scroll_ending_event_ = true;
+      break;
+    case ET_GESTURE_SCROLL_END:
+      needs_scroll_ending_event_ = false;
+      break;
+    case ET_SCROLL_FLING_START:
+      CancelFlingIfNecessary(packet_being_sent);
+      ending_event_motion_event_id_ = event.motion_event_id;
+      ending_event_primary_tool_type_ = event.primary_tool_type;
+      needs_fling_ending_event_ = true;
+      needs_scroll_ending_event_ = false;
+      break;
+    case ET_SCROLL_FLING_CANCEL:
+      needs_fling_ending_event_ = false;
+      break;
+    default:
+      break;
+  }
+  client_->ForwardGestureEvent(event);
+}
+
+void TouchDispositionGestureFilter::CancelTapIfNecessary(
+    const GestureEventDataPacket& packet_being_sent) {
+  if (!needs_tap_ending_event_)
+    return;
+
+  SendGesture(CreateGesture(ET_GESTURE_TAP_CANCEL,
+                            ending_event_motion_event_id_,
+                            ending_event_primary_tool_type_,
+                            packet_being_sent),
+              packet_being_sent);
+  DCHECK(!needs_tap_ending_event_);
+}
+
+void TouchDispositionGestureFilter::CancelFlingIfNecessary(
+    const GestureEventDataPacket& packet_being_sent) {
+  if (!needs_fling_ending_event_)
+    return;
+
+  SendGesture(CreateGesture(ET_SCROLL_FLING_CANCEL,
+                            ending_event_motion_event_id_,
+                            ending_event_primary_tool_type_,
+                            packet_being_sent),
+              packet_being_sent);
+  DCHECK(!needs_fling_ending_event_);
+}
+
+void TouchDispositionGestureFilter::EndScrollIfNecessary(
+    const GestureEventDataPacket& packet_being_sent) {
+  if (!needs_scroll_ending_event_)
+    return;
+
+  SendGesture(CreateGesture(ET_GESTURE_SCROLL_END,
+                            ending_event_motion_event_id_,
+                            ending_event_primary_tool_type_,
+                            packet_being_sent),
+              packet_being_sent);
+  DCHECK(!needs_scroll_ending_event_);
+}
+
+void TouchDispositionGestureFilter::PopGestureSequence() {
+  DCHECK(Head().empty());
+  state_ = GestureHandlingState();
+  sequences_.pop();
+}
+
+TouchDispositionGestureFilter::GestureSequence&
+TouchDispositionGestureFilter::Head() {
+  DCHECK(!sequences_.empty());
+  return sequences_.front();
+}
+
+TouchDispositionGestureFilter::GestureSequence&
+TouchDispositionGestureFilter::Tail() {
+  DCHECK(!sequences_.empty());
+  return sequences_.back();
+}
+
+// TouchDispositionGestureFilter::GestureHandlingState
+
+TouchDispositionGestureFilter::GestureHandlingState::GestureHandlingState()
+    : start_touch_consumed_(false),
+      current_touch_consumed_(false) {}
+
+void TouchDispositionGestureFilter::GestureHandlingState::OnTouchEventAck(
+    bool event_consumed,
+    bool is_touch_start_event) {
+  current_touch_consumed_ = event_consumed;
+  if (event_consumed && is_touch_start_event)
+    start_touch_consumed_ = true;
+}
+
+bool TouchDispositionGestureFilter::GestureHandlingState::Filter(
+    EventType gesture_type) {
+  DispositionHandlingInfo disposition_handling_info =
+      GetDispositionHandlingInfo(gesture_type);
+
+  int required_touches = disposition_handling_info.required_touches;
+  EventType antecedent_event_type =
+      disposition_handling_info.antecedent_event_type;
+  if ((required_touches & RT_START && start_touch_consumed_) ||
+      (required_touches & RT_CURRENT && current_touch_consumed_) ||
+      (antecedent_event_type != ET_UNKNOWN &&
+       last_gesture_of_type_dropped_.has_bit(
+           GetGestureTypeIndex(antecedent_event_type)))) {
+    last_gesture_of_type_dropped_.mark_bit(GetGestureTypeIndex(gesture_type));
+    return true;
+  }
+  last_gesture_of_type_dropped_.clear_bit(GetGestureTypeIndex(gesture_type));
+  return false;
+}
+
+}  // namespace content
diff --git a/ui/events/gesture_detection/touch_disposition_gesture_filter.h b/ui/events/gesture_detection/touch_disposition_gesture_filter.h
new file mode 100644
index 0000000..290afaa
--- /dev/null
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter.h
@@ -0,0 +1,109 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_TOUCH_DISPOSITION_GESTURE_FILTER_H_
+#define UI_EVENTS_GESTURE_DETECTION_TOUCH_DISPOSITION_GESTURE_FILTER_H_
+
+#include <queue>
+
+#include "ui/events/event_constants.h"
+#include "ui/events/gesture_detection/bitset_32.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_event_data_packet.h"
+
+namespace ui {
+
+// Interface with which the |TouchDispositionGestureFilter| forwards gestures
+// for a given touch event.
+class GESTURE_DETECTION_EXPORT TouchDispositionGestureFilterClient {
+ public:
+  virtual void ForwardGestureEvent(const GestureEventData&) = 0;
+};
+
+// Given a stream of touch-derived gesture packets, produces a refined gesture
+// sequence based on the ack dispositions of the generating touch events.
+class GESTURE_DETECTION_EXPORT TouchDispositionGestureFilter {
+ public:
+  explicit TouchDispositionGestureFilter(
+      TouchDispositionGestureFilterClient* client);
+  ~TouchDispositionGestureFilter();
+
+  // To be called upon production of touch-derived gestures by the platform,
+  // *prior* to the generating touch being forward to the renderer.  In
+  // particular, |packet| contains [0, n] gestures that correspond to a given
+  // touch event. It is imperative that a single packet is received for
+  // *each* touch event, even those that did not produce a gesture.
+  enum PacketResult {
+    SUCCESS,              // Packet successfully queued.
+    INVALID_PACKET_ORDER, // Packets were received in the wrong order, i.e.,
+                          // TOUCH_BEGIN should always precede other packets.
+    INVALID_PACKET_TYPE,  // Packet had an invalid type.
+  };
+  PacketResult OnGesturePacket(const GestureEventDataPacket& packet);
+
+  // To be called upon receipt of *all* touch event acks.
+  void OnTouchEventAck(bool event_consumed);
+
+  // Whether there are any active gesture sequences still queued in the filter.
+  bool IsEmpty() const;
+
+ private:
+  // A single GestureSequence corresponds to all gestures created
+  // between the first finger down and the last finger up, including gestures
+  // generated by timeouts from a statinoary finger.
+  typedef std::queue<GestureEventDataPacket> GestureSequence;
+
+  // Utility class for maintaining the touch and gesture handling state for the
+  // current gesture sequence.
+  class GestureHandlingState {
+   public:
+    GestureHandlingState();
+
+    // To be called on each touch event ack.
+    void OnTouchEventAck(bool event_consumed, bool is_touch_start_event);
+
+    // Returns true iff the gesture should be dropped.
+    bool Filter(EventType type);
+
+   private:
+    // True iff the sequence has had any touch down event consumed.
+    bool start_touch_consumed_;
+    // True iff the most recently ack'ed touch event was consumed.
+    bool current_touch_consumed_;
+    // If the previous gesture of a given type was dropped instead of being
+    // dispatched, its type will occur in this set.
+    BitSet32 last_gesture_of_type_dropped_;
+  };
+
+  void FilterAndSendPacket(const GestureEventDataPacket& packet);
+  void SendGesture(const GestureEventData& gesture,
+                   const GestureEventDataPacket& packet);
+  void CancelTapIfNecessary(const GestureEventDataPacket& packet);
+  void CancelFlingIfNecessary(const GestureEventDataPacket& packet);
+  void EndScrollIfNecessary(const GestureEventDataPacket& packet);
+  void PopGestureSequence();
+  GestureSequence& Head();
+  GestureSequence& Tail();
+
+  TouchDispositionGestureFilterClient* client_;
+  std::queue<GestureSequence> sequences_;
+
+  GestureHandlingState state_;
+
+  // Bookkeeping for inserting synthetic Gesture{Tap,Fling}Cancel events
+  // when necessary, e.g., GestureTapCancel when scrolling begins, or
+  // GestureFlingCancel when a user taps following a GestureFlingStart.
+  int ending_event_motion_event_id_;
+  MotionEvent::ToolType ending_event_primary_tool_type_;
+  bool needs_tap_ending_event_;
+  bool needs_show_press_event_;
+  bool needs_fling_ending_event_;
+  bool needs_scroll_ending_event_;
+
+  DISALLOW_COPY_AND_ASSIGN(TouchDispositionGestureFilter);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_TOUCH_DISPOSITION_GESTURE_FILTER_H_
diff --git a/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc b/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
new file mode 100644
index 0000000..7e99c94
--- /dev/null
+++ b/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
@@ -0,0 +1,1153 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h"
+#include "ui/events/test/mock_motion_event.h"
+
+using ui::test::MockMotionEvent;
+
+namespace ui {
+namespace {
+
+const int kDefaultEventFlags = EF_ALT_DOWN | EF_SHIFT_DOWN;
+
+}  // namespace
+
+class TouchDispositionGestureFilterTest
+    : public testing::Test,
+      public TouchDispositionGestureFilterClient {
+ public:
+  TouchDispositionGestureFilterTest()
+      : cancel_after_next_gesture_(false), sent_gesture_count_(0) {}
+  virtual ~TouchDispositionGestureFilterTest() {}
+
+  // testing::Test
+  virtual void SetUp() OVERRIDE {
+    queue_.reset(new TouchDispositionGestureFilter(this));
+    touch_event_.set_flags(kDefaultEventFlags);
+  }
+
+  virtual void TearDown() OVERRIDE {
+    queue_.reset();
+  }
+
+  // TouchDispositionGestureFilterClient
+  virtual void ForwardGestureEvent(const GestureEventData& event) OVERRIDE {
+    ++sent_gesture_count_;
+    last_sent_gesture_.reset(new GestureEventData(event));
+    sent_gestures_.push_back(event.type());
+    if (event.type() == ET_GESTURE_SHOW_PRESS)
+      show_press_bounding_box_ = event.details.bounding_box();
+    if (cancel_after_next_gesture_) {
+      cancel_after_next_gesture_ = false;
+      CancelTouchPoint();
+      SendTouchNotConsumedAck();
+    }
+  }
+
+ protected:
+  typedef std::vector<EventType> GestureList;
+
+  ::testing::AssertionResult GesturesMatch(const GestureList& expected,
+                                           const GestureList& actual) {
+    if (expected.size() != actual.size()) {
+      return ::testing::AssertionFailure()
+          << "actual.size(" << actual.size()
+          << ") != expected.size(" << expected.size() << ")";
+    }
+
+    for (size_t i = 0; i < expected.size(); ++i) {
+      if (expected[i] != actual[i]) {
+        return ::testing::AssertionFailure()
+            << "actual[" << i << "] ("
+            << actual[i]
+            << ") != expected[" << i << "] ("
+            << expected[i] << ")";
+      }
+    }
+
+    return ::testing::AssertionSuccess();
+  }
+
+  GestureList Gestures(EventType type) {
+    return GestureList(1, type);
+  }
+
+  GestureList Gestures(EventType type0, EventType type1) {
+    GestureList gestures(2);
+    gestures[0] = type0;
+    gestures[1] = type1;
+    return gestures;
+  }
+
+  GestureList Gestures(EventType type0,
+                       EventType type1,
+                       EventType type2) {
+    GestureList gestures(3);
+    gestures[0] = type0;
+    gestures[1] = type1;
+    gestures[2] = type2;
+    return gestures;
+  }
+
+  GestureList Gestures(EventType type0,
+                       EventType type1,
+                       EventType type2,
+                       EventType type3) {
+    GestureList gestures(4);
+    gestures[0] = type0;
+    gestures[1] = type1;
+    gestures[2] = type2;
+    gestures[3] = type3;
+    return gestures;
+  }
+
+  void SendTouchGestures() {
+    touch_event_.set_event_time(base::TimeTicks::Now());
+    EXPECT_EQ(TouchDispositionGestureFilter::SUCCESS,
+              SendTouchGestures(touch_event_, pending_gesture_packet_));
+    GestureEventDataPacket gesture_packet;
+    std::swap(gesture_packet, pending_gesture_packet_);
+  }
+
+  TouchDispositionGestureFilter::PacketResult
+  SendTouchGestures(const MotionEvent& touch,
+                    const GestureEventDataPacket& packet) {
+    GestureEventDataPacket touch_packet =
+        GestureEventDataPacket::FromTouch(touch);
+    for (size_t i = 0; i < packet.gesture_count(); ++i)
+      touch_packet.Push(packet.gesture(i));
+    return queue_->OnGesturePacket(touch_packet);
+  }
+
+  TouchDispositionGestureFilter::PacketResult
+  SendTimeoutGesture(EventType type) {
+    return queue_->OnGesturePacket(
+        GestureEventDataPacket::FromTouchTimeout(CreateGesture(type)));
+  }
+
+  TouchDispositionGestureFilter::PacketResult
+  SendGesturePacket(const GestureEventDataPacket& packet) {
+    return queue_->OnGesturePacket(packet);
+  }
+
+  void SendTouchEventAck(bool event_consumed) {
+    queue_->OnTouchEventAck(event_consumed);
+  }
+
+  void SendTouchConsumedAck() { SendTouchEventAck(true); }
+
+  void SendTouchNotConsumedAck() { SendTouchEventAck(false); }
+
+  void PushGesture(EventType type) {
+    pending_gesture_packet_.Push(CreateGesture(type));
+  }
+
+  void PushGesture(EventType type, float x, float y, float diameter) {
+    pending_gesture_packet_.Push(CreateGesture(type, x, y, diameter));
+  }
+
+  void PressTouchPoint(int x, int y) {
+    touch_event_.PressPoint(x, y);
+    touch_event_.SetRawOffset(raw_offset_.x(), raw_offset_.y());
+    SendTouchGestures();
+  }
+
+  void MoveTouchPoint(size_t index, int x, int y) {
+    touch_event_.MovePoint(index, x, y);
+    touch_event_.SetRawOffset(raw_offset_.x(), raw_offset_.y());
+    SendTouchGestures();
+  }
+
+  void ReleaseTouchPoint() {
+    touch_event_.ReleasePoint();
+    SendTouchGestures();
+  }
+
+  void CancelTouchPoint() {
+    touch_event_.CancelPoint();
+    SendTouchGestures();
+  }
+
+  void SetRawTouchOffset(const gfx::Vector2dF& raw_offset) {
+    raw_offset_ = raw_offset;
+  }
+
+  void ResetTouchPoints() { touch_event_ = MockMotionEvent(); }
+
+  bool GesturesSent() const { return !sent_gestures_.empty(); }
+
+  base::TimeTicks LastSentGestureTime() const {
+    CHECK(last_sent_gesture_);
+    return last_sent_gesture_->time;
+  }
+
+  base::TimeTicks CurrentTouchTime() const {
+    return touch_event_.GetEventTime();
+  }
+
+  bool IsEmpty() const { return queue_->IsEmpty(); }
+
+  GestureList GetAndResetSentGestures() {
+    GestureList sent_gestures;
+    sent_gestures.swap(sent_gestures_);
+    return sent_gestures;
+  }
+
+  gfx::PointF LastSentGestureLocation() const {
+    CHECK(last_sent_gesture_);
+    return gfx::PointF(last_sent_gesture_->x, last_sent_gesture_->y);
+  }
+
+  gfx::PointF LastSentGestureRawLocation() const {
+    CHECK(last_sent_gesture_);
+    return gfx::PointF(last_sent_gesture_->raw_x, last_sent_gesture_->raw_y);
+  }
+
+  int LastSentGestureFlags() const {
+    CHECK(last_sent_gesture_);
+    return last_sent_gesture_->flags;
+  }
+
+  const gfx::RectF& ShowPressBoundingBox() const {
+    return show_press_bounding_box_;
+  }
+
+  void SetCancelAfterNextGesture(bool cancel_after_next_gesture) {
+    cancel_after_next_gesture_ = cancel_after_next_gesture;
+  }
+
+  GestureEventData CreateGesture(EventType type) {
+    return CreateGesture(type, 0, 0, 0);
+  }
+
+  GestureEventData CreateGesture(EventType type,
+                                 float x,
+                                 float y,
+                                 float diameter) {
+    return GestureEventData(
+        GestureEventDetails(type),
+        0,
+        MotionEvent::TOOL_TYPE_FINGER,
+        base::TimeTicks(),
+        touch_event_.GetX(0),
+        touch_event_.GetY(0),
+        touch_event_.GetRawX(0),
+        touch_event_.GetRawY(0),
+        1,
+        gfx::RectF(x - diameter / 2, y - diameter / 2, diameter, diameter),
+        kDefaultEventFlags);
+  }
+
+ private:
+  scoped_ptr<TouchDispositionGestureFilter> queue_;
+  bool cancel_after_next_gesture_;
+  MockMotionEvent touch_event_;
+  GestureEventDataPacket pending_gesture_packet_;
+  size_t sent_gesture_count_;
+  GestureList sent_gestures_;
+  gfx::Vector2dF raw_offset_;
+  scoped_ptr<GestureEventData> last_sent_gesture_;
+  gfx::RectF show_press_bounding_box_;
+};
+
+TEST_F(TouchDispositionGestureFilterTest, BasicNoGestures) {
+  PressTouchPoint(1, 1);
+  EXPECT_FALSE(GesturesSent());
+
+  MoveTouchPoint(0, 2, 2);
+  EXPECT_FALSE(GesturesSent());
+
+  // No gestures should be dispatched by the ack, as the queued packets
+  // contained no gestures.
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // Release the touch gesture.
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, BasicGestures) {
+  // An unconsumed touch's gesture should be sent.
+  PushGesture(ET_GESTURE_BEGIN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  EXPECT_FALSE(GesturesSent());
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // Multiple gestures can be queued for a single event.
+  PushGesture(ET_SCROLL_FLING_START);
+  PushGesture(ET_SCROLL_FLING_CANCEL);
+  PushGesture(ET_GESTURE_END);
+  ReleaseTouchPoint();
+  EXPECT_FALSE(GesturesSent());
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_START,
+                                     ET_SCROLL_FLING_CANCEL,
+                                     ET_GESTURE_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, BasicGesturesConsumed) {
+  // A consumed touch's gesture should not be sent.
+  PushGesture(ET_GESTURE_BEGIN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_SCROLL_FLING_START);
+  PushGesture(ET_SCROLL_FLING_CANCEL);
+  PushGesture(ET_GESTURE_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ConsumedThenNotConsumed) {
+  // A consumed touch's gesture should not be sent.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // Even if the subsequent touch is not consumed, continue dropping gestures.
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // Even if the subsequent touch had no consumer, continue dropping gestures.
+  PushGesture(ET_SCROLL_FLING_START);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, NotConsumedThenConsumed) {
+  // A not consumed touch's gesture should be sent.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // A newly consumed gesture should not be sent.
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(10, 10);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // And subsequent non-consumed pinch updates should not be sent.
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  PushGesture(ET_GESTURE_PINCH_UPDATE);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_UPDATE),
+                            GetAndResetSentGestures()));
+
+  // End events dispatched only when their start events were.
+  PushGesture(ET_GESTURE_PINCH_END);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ScrollAlternatelyConsumed) {
+  // A consumed touch's gesture should not be sent.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  for (size_t i = 0; i < 3; ++i) {
+    PushGesture(ET_GESTURE_SCROLL_UPDATE);
+    MoveTouchPoint(0, 2, 2);
+    SendTouchConsumedAck();
+    EXPECT_FALSE(GesturesSent());
+
+    PushGesture(ET_GESTURE_SCROLL_UPDATE);
+    MoveTouchPoint(0, 3, 3);
+    SendTouchNotConsumedAck();
+    EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_UPDATE),
+                              GetAndResetSentGestures()));
+  }
+
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, NotConsumedThenNoConsumer) {
+  // An unconsumed touch's gesture should be sent.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // If the subsequent touch has no consumer (e.g., a secondary pointer is
+  // pressed but not on a touch handling rect), send the gesture.
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // End events should be dispatched when their start events were, independent
+  // of the ack state.
+  PushGesture(ET_GESTURE_PINCH_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_END),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, EndingEventsSent) {
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // Consuming the touchend event can't suppress the match end gesture.
+  PushGesture(ET_GESTURE_PINCH_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_END),
+                            GetAndResetSentGestures()));
+
+  // But other events in the same packet are still suppressed.
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+
+  // ET_GESTURE_SCROLL_END and ET_SCROLL_FLING_START behave the same in this
+  // regard.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_SCROLL_FLING_START);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_START),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, EndingEventsNotSent) {
+  // Consuming a begin event ensures no end events are sent.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_PINCH_END);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, UpdateEventsSuppressedPerEvent) {
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // Consuming a single scroll or pinch update should suppress only that event.
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_PINCH_UPDATE);
+  MoveTouchPoint(1, 2, 3);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // Subsequent updates should not be affected.
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 4, 4);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_UPDATE),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_PINCH_UPDATE);
+  MoveTouchPoint(0, 4, 5);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_UPDATE),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_PINCH_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_END),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, UpdateEventsDependOnBeginEvents) {
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // Scroll and pinch gestures depend on the scroll begin gesture being
+  // dispatched.
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_PINCH_UPDATE);
+  MoveTouchPoint(1, 2, 3);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_PINCH_END);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, MultipleTouchSequences) {
+  // Queue two touch-to-gestures sequences.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  PushGesture(ET_GESTURE_TAP);
+  ReleaseTouchPoint();
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  PushGesture(ET_GESTURE_SCROLL_END);
+  ReleaseTouchPoint();
+
+  // The first gesture sequence should not be allowed.
+  SendTouchConsumedAck();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  // The subsequent sequence should "reset" allowance.
+  SendTouchNotConsumedAck();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN,
+                                     ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, FlingCancelledOnNewTouchSequence) {
+  const gfx::Vector2dF raw_offset(1.3f, 3.7f);
+  SetRawTouchOffset(raw_offset);
+
+  // Simulate a fling.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(
+      Gestures(
+          ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN),
+      GetAndResetSentGestures()));
+  PushGesture(ET_SCROLL_FLING_START);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_START),
+                            GetAndResetSentGestures()));
+
+  // A new touch sequence should cancel the outstanding fling.
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_CANCEL),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime());
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+  EXPECT_EQ(LastSentGestureRawLocation(), gfx::PointF(1, 1) + raw_offset);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ScrollEndedOnTouchReleaseIfNoFling) {
+  // Simulate a scroll.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(
+      Gestures(
+          ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN),
+      GetAndResetSentGestures()));
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime());
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ScrollEndedOnNewTouchSequence) {
+  // Simulate a scroll.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(
+      Gestures(
+          ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN),
+      GetAndResetSentGestures()));
+
+  // A new touch sequence should end the outstanding scroll.
+  ResetTouchPoints();
+  PressTouchPoint(2, 3);
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime());
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 3));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, FlingCancelledOnScrollBegin) {
+  // Simulate a fling sequence.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PushGesture(ET_SCROLL_FLING_START);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN,
+                                     ET_GESTURE_TAP_CANCEL,
+                                     ET_GESTURE_SCROLL_BEGIN,
+                                     ET_SCROLL_FLING_START),
+                            GetAndResetSentGestures()));
+
+  // The new fling should cancel the preceding one.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PushGesture(ET_SCROLL_FLING_START);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_SCROLL_FLING_CANCEL,
+                                     ET_GESTURE_SCROLL_BEGIN,
+                                     ET_SCROLL_FLING_START),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, FlingNotCancelledIfGFCEventReceived) {
+  // Simulate a fling that is started then cancelled.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  PushGesture(ET_SCROLL_FLING_START);
+  MoveTouchPoint(0, 2, 3);
+  SendTouchNotConsumedAck();
+  PushGesture(ET_SCROLL_FLING_CANCEL);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN,
+                                     ET_SCROLL_FLING_START,
+                                     ET_SCROLL_FLING_CANCEL),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 3));
+
+  // A new touch sequence will not inject a ET_SCROLL_FLING_CANCEL, as the fling
+  // has already been cancelled.
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapCancelledWhenScrollBegins) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  // If the subsequent touch turns into a scroll, the tap should be cancelled.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL,
+                                     ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapCancelledWhenTouchConsumed) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  // If the subsequent touch is consumed, the tap should be cancelled.
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchConsumedAck();
+  EXPECT_TRUE(
+      GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL, ET_GESTURE_SCROLL_BEGIN),
+                    GetAndResetSentGestures()));
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+}
+
+TEST_F(TouchDispositionGestureFilterTest,
+       TapNotCancelledIfTapEndingEventReceived) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(
+      GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_TAP);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, ET_GESTURE_TAP),
+                            GetAndResetSentGestures()));
+
+  // The tap should not be cancelled as it was terminated by a |ET_GESTURE_TAP|.
+  PressTouchPoint(2, 2);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TimeoutGestures) {
+  // If the sequence is allowed, and there are no preceding gestures, the
+  // timeout gestures should be forwarded immediately.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  SendTimeoutGesture(ET_GESTURE_SHOW_PRESS);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS),
+                            GetAndResetSentGestures()));
+
+  SendTimeoutGesture(ET_GESTURE_LONG_PRESS);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_LONG_PRESS),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_LONG_TAP);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL,
+                                     ET_GESTURE_LONG_TAP),
+                            GetAndResetSentGestures()));
+
+  // If the sequence is disallowed, and there are no preceding gestures, the
+  // timeout gestures should be dropped immediately.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  SendTimeoutGesture(ET_GESTURE_SHOW_PRESS);
+  EXPECT_FALSE(GesturesSent());
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+
+  // If the sequence has a pending ack, the timeout gestures should
+  // remain queued until the ack is received.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  EXPECT_FALSE(GesturesSent());
+
+  SendTimeoutGesture(ET_GESTURE_LONG_PRESS);
+  EXPECT_FALSE(GesturesSent());
+
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN,
+                                     ET_GESTURE_LONG_PRESS),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, SpuriousAcksIgnored) {
+  // Acks received when the queue is empty will be safely ignored.
+  ASSERT_TRUE(IsEmpty());
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 3,3);
+  SendTouchNotConsumedAck();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN,
+                                     ET_GESTURE_SCROLL_UPDATE),
+                            GetAndResetSentGestures()));
+
+  // Even if all packets have been dispatched, the filter may not be empty as
+  // there could be follow-up timeout events.  Spurious acks in such cases
+  // should also be safely ignored.
+  ASSERT_FALSE(IsEmpty());
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, PacketWithInvalidTypeIgnored) {
+  GestureEventDataPacket packet;
+  EXPECT_EQ(TouchDispositionGestureFilter::INVALID_PACKET_TYPE,
+            SendGesturePacket(packet));
+  EXPECT_TRUE(IsEmpty());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, PacketsWithInvalidOrderIgnored) {
+  EXPECT_EQ(TouchDispositionGestureFilter::INVALID_PACKET_ORDER,
+            SendTimeoutGesture(ET_GESTURE_SHOW_PRESS));
+  EXPECT_TRUE(IsEmpty());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ConsumedTouchCancel) {
+  // An unconsumed touch's gesture should be sent.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  EXPECT_FALSE(GesturesSent());
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_TAP_CANCEL);
+  PushGesture(ET_GESTURE_SCROLL_END);
+  CancelTouchPoint();
+  EXPECT_FALSE(GesturesSent());
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL,
+                                     ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TimeoutEventAfterRelease) {
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PushGesture(ET_GESTURE_TAP_UNCONFIRMED);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(
+      GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN, ET_GESTURE_TAP_UNCONFIRMED),
+                    GetAndResetSentGestures()));
+
+  SendTimeoutGesture(ET_GESTURE_TAP);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, ET_GESTURE_TAP),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ShowPressInsertedBeforeTap) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  SendTimeoutGesture(ET_GESTURE_TAP_UNCONFIRMED);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_UNCONFIRMED),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_TAP);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS,
+                                     ET_GESTURE_TAP),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ShowPressNotInsertedIfAlreadySent) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  SendTimeoutGesture(ET_GESTURE_SHOW_PRESS);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_TAP);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapAndScrollCancelledOnTouchCancel) {
+  const gfx::Vector2dF raw_offset(1.3f, 3.7f);
+  SetRawTouchOffset(raw_offset);
+
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  // A cancellation motion event should cancel the tap.
+  CancelTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime());
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+  EXPECT_EQ(LastSentGestureRawLocation(), gfx::PointF(1, 1) + raw_offset);
+
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  // A cancellation motion event should end the scroll, even if the touch was
+  // consumed.
+  CancelTouchPoint();
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime());
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+  EXPECT_EQ(LastSentGestureRawLocation(), gfx::PointF(1, 1) + raw_offset);
+}
+
+TEST_F(TouchDispositionGestureFilterTest,
+       ConsumedScrollUpdateMakesFlingScrollEnd) {
+  // A consumed touch's gesture should not be sent.
+  PushGesture(ET_GESTURE_BEGIN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+
+  EXPECT_TRUE(
+      GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_SCROLL_BEGIN),
+                    GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_SCROLL_UPDATE);
+  MoveTouchPoint(0, 2, 2);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+
+  PushGesture(ET_SCROLL_FLING_START);
+  PushGesture(ET_SCROLL_FLING_CANCEL);
+  PushGesture(ET_GESTURE_END);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_END, ET_GESTURE_END),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 2));
+
+  PushGesture(ET_GESTURE_BEGIN);
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapCancelledOnTouchCancel) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  // A cancellation motion event should cancel the tap.
+  CancelTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(CurrentTouchTime(), LastSentGestureTime());
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+}
+
+// Test that a GestureEvent whose dispatch causes a cancel event to be fired
+// won't cause a crash.
+TEST_F(TouchDispositionGestureFilterTest, TestCancelMidGesture) {
+  SetCancelAfterNextGesture(true);
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN,
+                                     ET_GESTURE_TAP_CANCEL),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(1, 1));
+}
+
+// Test that a MultiFingerSwipe event is dispatched when appropriate.
+TEST_F(TouchDispositionGestureFilterTest, TestAllowedMultiFingerSwipe) {
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_SWIPE);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SWIPE),
+                            GetAndResetSentGestures()));
+}
+
+  // Test that a MultiFingerSwipe event is dispatched when appropriate.
+TEST_F(TouchDispositionGestureFilterTest, TestDisallowedMultiFingerSwipe) {
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+
+  PushGesture(ET_GESTURE_SCROLL_BEGIN);
+  MoveTouchPoint(0, 0, 0);
+  SendTouchConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SCROLL_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_PINCH_BEGIN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_PINCH_BEGIN),
+                            GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_SWIPE);
+  PressTouchPoint(1, 1);
+  SendTouchConsumedAck();
+  EXPECT_FALSE(GesturesSent());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapCancelOnSecondFingerDown) {
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, ShowPressBoundingBox) {
+  PushGesture(ET_GESTURE_TAP_DOWN, 9, 9, 8);
+  PressTouchPoint(9, 9);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(
+      GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), GetAndResetSentGestures()));
+
+  PushGesture(ET_GESTURE_TAP, 10, 10, 10);
+  ReleaseTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS, ET_GESTURE_TAP),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(gfx::RectF(5, 5, 10, 10), ShowPressBoundingBox());
+}
+
+TEST_F(TouchDispositionGestureFilterTest, TapCancelledBeforeGestureEnd) {
+  PushGesture(ET_GESTURE_BEGIN);
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_BEGIN, ET_GESTURE_TAP_DOWN),
+                            GetAndResetSentGestures()));
+  SendTimeoutGesture(ET_GESTURE_SHOW_PRESS);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_SHOW_PRESS),
+                            GetAndResetSentGestures()));
+
+  SendTimeoutGesture(ET_GESTURE_LONG_PRESS);
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_LONG_PRESS),
+                            GetAndResetSentGestures()));
+  PushGesture(ET_GESTURE_END);
+  CancelTouchPoint();
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL, ET_GESTURE_END),
+                            GetAndResetSentGestures()));
+}
+
+TEST_F(TouchDispositionGestureFilterTest, EventFlagPropagation) {
+  // Real gestures should propagate flags from their causal touches.
+  PushGesture(ET_GESTURE_TAP_DOWN);
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(
+      GesturesMatch(Gestures(ET_GESTURE_TAP_DOWN), GetAndResetSentGestures()));
+  EXPECT_EQ(kDefaultEventFlags, LastSentGestureFlags());
+
+  // Synthetic gestures lack flags.
+  PressTouchPoint(1, 1);
+  SendTouchNotConsumedAck();
+  EXPECT_TRUE(GesturesMatch(Gestures(ET_GESTURE_TAP_CANCEL),
+                            GetAndResetSentGestures()));
+  EXPECT_EQ(0, LastSentGestureFlags());
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/velocity_tracker.cc b/ui/events/gesture_detection/velocity_tracker.cc
new file mode 100644
index 0000000..16454e8
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker.cc
@@ -0,0 +1,818 @@
+// Copyright 2014 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 "ui/events/gesture_detection/velocity_tracker.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace ui {
+
+// Implements a particular velocity tracker algorithm.
+class VelocityTrackerStrategy {
+ public:
+  virtual ~VelocityTrackerStrategy() {}
+
+  virtual void Clear() = 0;
+  virtual void ClearPointers(BitSet32 id_bits) = 0;
+  virtual void AddMovement(const base::TimeTicks& event_time,
+                           BitSet32 id_bits,
+                           const Position* positions) = 0;
+  virtual bool GetEstimator(uint32_t id, Estimator* out_estimator) const = 0;
+
+ protected:
+  VelocityTrackerStrategy() {}
+};
+
+namespace {
+
+COMPILE_ASSERT(MotionEvent::MAX_POINTER_ID < 32, max_pointer_id_too_large);
+
+// Threshold between ACTION_MOVE events for determining that a pointer has
+// stopped moving. Some input devices do not send ACTION_MOVE events in the case
+// where a pointer has stopped.  We need to detect this case so that we can
+// accurately predict the velocity after the pointer starts moving again.
+const int kAssumePointerMoveStoppedTimeMs = 40;
+
+// Threshold between ACTION_MOVE and ACTION_{UP|POINTER_UP} events for
+// determining that a pointer has stopped moving. This is a larger threshold
+// than |kAssumePointerMoveStoppedTimeMs|, as some devices may delay synthesis
+// of ACTION_{UP|POINTER_UP} to reduce risk of noisy release.
+const int kAssumePointerUpStoppedTimeMs = 80;
+
+struct Position {
+  float x, y;
+};
+
+struct Estimator {
+  enum { MAX_DEGREE = 4 };
+
+  // Estimator time base.
+  TimeTicks time;
+
+  // Polynomial coefficients describing motion in X and Y.
+  float xcoeff[MAX_DEGREE + 1], ycoeff[MAX_DEGREE + 1];
+
+  // Polynomial degree (number of coefficients), or zero if no information is
+  // available.
+  uint32_t degree;
+
+  // Confidence (coefficient of determination), between 0 (no fit)
+  // and 1 (perfect fit).
+  float confidence;
+
+  inline void Clear() {
+    time = TimeTicks();
+    degree = 0;
+    confidence = 0;
+    for (size_t i = 0; i <= MAX_DEGREE; i++) {
+      xcoeff[i] = 0;
+      ycoeff[i] = 0;
+    }
+  }
+};
+
+float VectorDot(const float* a, const float* b, uint32_t m) {
+  float r = 0;
+  while (m--) {
+    r += *(a++) * *(b++);
+  }
+  return r;
+}
+
+float VectorNorm(const float* a, uint32_t m) {
+  float r = 0;
+  while (m--) {
+    float t = *(a++);
+    r += t * t;
+  }
+  return sqrtf(r);
+}
+
+// Velocity tracker algorithm based on least-squares linear regression.
+class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
+ public:
+  enum Weighting {
+    // No weights applied.  All data points are equally reliable.
+    WEIGHTING_NONE,
+
+    // Weight by time delta.  Data points clustered together are weighted less.
+    WEIGHTING_DELTA,
+
+    // Weight such that points within a certain horizon are weighed more than
+    // those outside of that horizon.
+    WEIGHTING_CENTRAL,
+
+    // Weight such that points older than a certain amount are weighed less.
+    WEIGHTING_RECENT,
+  };
+
+  // Number of samples to keep.
+  enum { HISTORY_SIZE = 20 };
+
+  // Degree must be no greater than Estimator::MAX_DEGREE.
+  LeastSquaresVelocityTrackerStrategy(uint32_t degree,
+                                      Weighting weighting = WEIGHTING_NONE);
+  virtual ~LeastSquaresVelocityTrackerStrategy();
+
+  virtual void Clear() OVERRIDE;
+  virtual void ClearPointers(BitSet32 id_bits) OVERRIDE;
+  virtual void AddMovement(const TimeTicks& event_time,
+                           BitSet32 id_bits,
+                           const Position* positions) OVERRIDE;
+  virtual bool GetEstimator(uint32_t id,
+                            Estimator* out_estimator) const OVERRIDE;
+
+ private:
+  // Sample horizon.
+  // We don't use too much history by default since we want to react to quick
+  // changes in direction.
+  enum { HORIZON_MS = 100 };
+
+  struct Movement {
+    TimeTicks event_time;
+    BitSet32 id_bits;
+    Position positions[VelocityTracker::MAX_POINTERS];
+
+    inline const Position& GetPosition(uint32_t id) const {
+      return positions[id_bits.get_index_of_bit(id)];
+    }
+  };
+
+  float ChooseWeight(uint32_t index) const;
+
+  const uint32_t degree_;
+  const Weighting weighting_;
+  uint32_t index_;
+  Movement movements_[HISTORY_SIZE];
+};
+
+// Velocity tracker algorithm that uses an IIR filter.
+class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
+ public:
+  // Degree must be 1 or 2.
+  explicit IntegratingVelocityTrackerStrategy(uint32_t degree);
+  virtual ~IntegratingVelocityTrackerStrategy();
+
+  virtual void Clear() OVERRIDE;
+  virtual void ClearPointers(BitSet32 id_bits) OVERRIDE;
+  virtual void AddMovement(const TimeTicks& event_time,
+                           BitSet32 id_bits,
+                           const Position* positions) OVERRIDE;
+  virtual bool GetEstimator(uint32_t id,
+                            Estimator* out_estimator) const OVERRIDE;
+
+ private:
+  // Current state estimate for a particular pointer.
+  struct State {
+    TimeTicks update_time;
+    uint32_t degree;
+
+    float xpos, xvel, xaccel;
+    float ypos, yvel, yaccel;
+  };
+
+  const uint32_t degree_;
+  BitSet32 pointer_id_bits_;
+  State mPointerState[MotionEvent::MAX_POINTER_ID + 1];
+
+  void InitState(State& state,
+                 const TimeTicks& event_time,
+                 float xpos,
+                 float ypos) const;
+  void UpdateState(State& state,
+                   const TimeTicks& event_time,
+                   float xpos,
+                   float ypos) const;
+  void PopulateEstimator(const State& state, Estimator* out_estimator) const;
+};
+
+VelocityTrackerStrategy* CreateStrategy(VelocityTracker::Strategy strategy) {
+  switch (strategy) {
+    case VelocityTracker::LSQ1:
+      return new LeastSquaresVelocityTrackerStrategy(1);
+    case VelocityTracker::LSQ2:
+      return new LeastSquaresVelocityTrackerStrategy(2);
+    case VelocityTracker::LSQ3:
+      return new LeastSquaresVelocityTrackerStrategy(3);
+    case VelocityTracker::WLSQ2_DELTA:
+      return new LeastSquaresVelocityTrackerStrategy(
+          2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
+    case VelocityTracker::WLSQ2_CENTRAL:
+      return new LeastSquaresVelocityTrackerStrategy(
+          2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
+    case VelocityTracker::WLSQ2_RECENT:
+      return new LeastSquaresVelocityTrackerStrategy(
+          2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
+    case VelocityTracker::INT1:
+      return new IntegratingVelocityTrackerStrategy(1);
+    case VelocityTracker::INT2:
+      return new IntegratingVelocityTrackerStrategy(2);
+  }
+  NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy;
+  return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT);
+}
+
+}  // namespace
+
+// --- VelocityTracker ---
+
+VelocityTracker::VelocityTracker()
+    : current_pointer_id_bits_(0),
+      active_pointer_id_(-1),
+      strategy_(CreateStrategy(STRATEGY_DEFAULT)) {}
+
+VelocityTracker::VelocityTracker(Strategy strategy)
+    : current_pointer_id_bits_(0),
+      active_pointer_id_(-1),
+      strategy_(CreateStrategy(strategy)) {}
+
+VelocityTracker::~VelocityTracker() {}
+
+void VelocityTracker::Clear() {
+  current_pointer_id_bits_.clear();
+  active_pointer_id_ = -1;
+  strategy_->Clear();
+}
+
+void VelocityTracker::ClearPointers(BitSet32 id_bits) {
+  BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value);
+  current_pointer_id_bits_ = remaining_id_bits;
+
+  if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) {
+    active_pointer_id_ = !remaining_id_bits.is_empty()
+                             ? remaining_id_bits.first_marked_bit()
+                             : -1;
+  }
+
+  strategy_->ClearPointers(id_bits);
+}
+
+void VelocityTracker::AddMovement(const TimeTicks& event_time,
+                                  BitSet32 id_bits,
+                                  const Position* positions) {
+  while (id_bits.count() > MAX_POINTERS)
+    id_bits.clear_last_marked_bit();
+
+  if ((current_pointer_id_bits_.value & id_bits.value) &&
+      (event_time - last_event_time_) >=
+          base::TimeDelta::FromMilliseconds(kAssumePointerMoveStoppedTimeMs)) {
+    // We have not received any movements for too long. Assume that all pointers
+    // have stopped.
+    strategy_->Clear();
+  }
+  last_event_time_ = event_time;
+
+  current_pointer_id_bits_ = id_bits;
+  if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_))
+    active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit();
+
+  strategy_->AddMovement(event_time, id_bits, positions);
+}
+
+void VelocityTracker::AddMovement(const MotionEvent& event) {
+  int32_t actionMasked = event.GetAction();
+
+  switch (actionMasked) {
+    case MotionEvent::ACTION_DOWN:
+      // case MotionEvent::HOVER_ENTER:
+      // Clear all pointers on down before adding the new movement.
+      Clear();
+      break;
+    case MotionEvent::ACTION_POINTER_DOWN: {
+      // Start a new movement trace for a pointer that just went down.
+      // We do this on down instead of on up because the client may want to
+      // query the final velocity for a pointer that just went up.
+      BitSet32 downIdBits;
+      downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex()));
+      ClearPointers(downIdBits);
+      break;
+    }
+    case MotionEvent::ACTION_MOVE:
+      // case MotionEvent::ACTION_HOVER_MOVE:
+      break;
+    case MotionEvent::ACTION_UP:
+    case MotionEvent::ACTION_POINTER_UP:
+      // Note that ACTION_UP and ACTION_POINTER_UP always report the last known
+      // position of the pointers that went up.  ACTION_POINTER_UP does include
+      // the new position of pointers that remained down but we will also
+      // receive an ACTION_MOVE with this information if any of them actually
+      // moved.  Since we don't know how many pointers will be going up at once
+      // it makes sense to just wait for the following ACTION_MOVE before adding
+      // the movement. However, if the up event itself is delayed because of
+      // (difficult albeit possible) prolonged stationary screen contact, assume
+      // that motion has stopped.
+      if ((event.GetEventTime() - last_event_time_) >=
+          base::TimeDelta::FromMilliseconds(kAssumePointerUpStoppedTimeMs))
+        strategy_->Clear();
+      return;
+    default:
+      // Ignore all other actions because they do not convey any new information
+      // about pointer movement.  We also want to preserve the last known
+      // velocity of the pointers.
+      return;
+  }
+
+  size_t pointer_count = event.GetPointerCount();
+  if (pointer_count > MAX_POINTERS) {
+    pointer_count = MAX_POINTERS;
+  }
+
+  BitSet32 id_bits;
+  for (size_t i = 0; i < pointer_count; i++) {
+    id_bits.mark_bit(event.GetPointerId(i));
+  }
+
+  uint32_t pointer_index[MAX_POINTERS];
+  for (size_t i = 0; i < pointer_count; i++) {
+    pointer_index[i] = id_bits.get_index_of_bit(event.GetPointerId(i));
+  }
+
+  Position positions[MAX_POINTERS];
+  size_t historySize = event.GetHistorySize();
+  for (size_t h = 0; h < historySize; h++) {
+    for (size_t i = 0; i < pointer_count; i++) {
+      uint32_t index = pointer_index[i];
+      positions[index].x = event.GetHistoricalX(i, h);
+      positions[index].y = event.GetHistoricalY(i, h);
+    }
+    AddMovement(event.GetHistoricalEventTime(h), id_bits, positions);
+  }
+
+  for (size_t i = 0; i < pointer_count; i++) {
+    uint32_t index = pointer_index[i];
+    positions[index].x = event.GetX(i);
+    positions[index].y = event.GetY(i);
+  }
+  AddMovement(event.GetEventTime(), id_bits, positions);
+}
+
+bool VelocityTracker::GetVelocity(uint32_t id,
+                                  float* out_vx,
+                                  float* out_vy) const {
+  Estimator estimator;
+  if (GetEstimator(id, &estimator) && estimator.degree >= 1) {
+    *out_vx = estimator.xcoeff[1];
+    *out_vy = estimator.ycoeff[1];
+    return true;
+  }
+  *out_vx = 0;
+  *out_vy = 0;
+  return false;
+}
+
+void LeastSquaresVelocityTrackerStrategy::AddMovement(
+    const TimeTicks& event_time,
+    BitSet32 id_bits,
+    const Position* positions) {
+  if (++index_ == HISTORY_SIZE) {
+    index_ = 0;
+  }
+
+  Movement& movement = movements_[index_];
+  movement.event_time = event_time;
+  movement.id_bits = id_bits;
+  uint32_t count = id_bits.count();
+  for (uint32_t i = 0; i < count; i++) {
+    movement.positions[i] = positions[i];
+  }
+}
+
+bool VelocityTracker::GetEstimator(uint32_t id,
+                                   Estimator* out_estimator) const {
+  return strategy_->GetEstimator(id, out_estimator);
+}
+
+// --- LeastSquaresVelocityTrackerStrategy ---
+
+LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
+    uint32_t degree,
+    Weighting weighting)
+    : degree_(degree), weighting_(weighting) {
+  DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE));
+  Clear();
+}
+
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
+
+void LeastSquaresVelocityTrackerStrategy::Clear() {
+  index_ = 0;
+  movements_[0].id_bits.clear();
+}
+
+/**
+ * Solves a linear least squares problem to obtain a N degree polynomial that
+ * fits the specified input data as nearly as possible.
+ *
+ * Returns true if a solution is found, false otherwise.
+ *
+ * The input consists of two vectors of data points X and Y with indices 0..m-1
+ * along with a weight vector W of the same size.
+ *
+ * The output is a vector B with indices 0..n that describes a polynomial
+ * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1]
+ * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is
+ * minimized.
+ *
+ * Accordingly, the weight vector W should be initialized by the caller with the
+ * reciprocal square root of the variance of the error in each input data point.
+ * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 /
+ * stddev(Y[i]).
+ * The weights express the relative importance of each data point.  If the
+ * weights are* all 1, then the data points are considered to be of equal
+ * importance when fitting the polynomial.  It is a good idea to choose weights
+ * that diminish the importance of data points that may have higher than usual
+ * error margins.
+ *
+ * Errors among data points are assumed to be independent.  W is represented
+ * here as a vector although in the literature it is typically taken to be a
+ * diagonal matrix.
+ *
+ * That is to say, the function that generated the input data can be
+ * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
+ *
+ * The coefficient of determination (R^2) is also returned to describe the
+ * goodness of fit of the model for the given data.  It is a value between 0
+ * and 1, where 1 indicates perfect correspondence.
+ *
+ * This function first expands the X vector to a m by n matrix A such that
+ * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
+ * multiplies it by w[i]./
+ *
+ * Then it calculates the QR decomposition of A yielding an m by m orthonormal
+ * matrix Q and an m by n upper triangular matrix R.  Because R is upper
+ * triangular (lower part is all zeroes), we can simplify the decomposition into
+ * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1.
+ *
+ * Finally we solve the system of linear equations given by
+ * R1 B = (Qtranspose W Y) to find B.
+ *
+ * For efficiency, we lay out A and Q column-wise in memory because we
+ * frequently operate on the column vectors.  Conversely, we lay out R row-wise.
+ *
+ * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
+ * http://en.wikipedia.org/wiki/Gram-Schmidt
+ */
+static bool SolveLeastSquares(const float* x,
+                              const float* y,
+                              const float* w,
+                              uint32_t m,
+                              uint32_t n,
+                              float* out_b,
+                              float* out_det) {
+  // MSVC does not support variable-length arrays (used by the original Android
+  // implementation of this function).
+#if defined(COMPILER_MSVC)
+  enum {
+    M_ARRAY_LENGTH = LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE,
+    N_ARRAY_LENGTH = Estimator::MAX_DEGREE
+  };
+  DCHECK_LE(m, static_cast<uint32_t>(M_ARRAY_LENGTH));
+  DCHECK_LE(n, static_cast<uint32_t>(N_ARRAY_LENGTH));
+#else
+  const uint32_t M_ARRAY_LENGTH = m;
+  const uint32_t N_ARRAY_LENGTH = n;
+#endif
+
+  // Expand the X vector to a matrix A, pre-multiplied by the weights.
+  float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH];  // column-major order
+  for (uint32_t h = 0; h < m; h++) {
+    a[0][h] = w[h];
+    for (uint32_t i = 1; i < n; i++) {
+      a[i][h] = a[i - 1][h] * x[h];
+    }
+  }
+
+  // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
+
+  // Orthonormal basis, column-major order.
+  float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH];
+  // Upper triangular matrix, row-major order.
+  float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH];
+  for (uint32_t j = 0; j < n; j++) {
+    for (uint32_t h = 0; h < m; h++) {
+      q[j][h] = a[j][h];
+    }
+    for (uint32_t i = 0; i < j; i++) {
+      float dot = VectorDot(&q[j][0], &q[i][0], m);
+      for (uint32_t h = 0; h < m; h++) {
+        q[j][h] -= dot * q[i][h];
+      }
+    }
+
+    float norm = VectorNorm(&q[j][0], m);
+    if (norm < 0.000001f) {
+      // vectors are linearly dependent or zero so no solution
+      return false;
+    }
+
+    float invNorm = 1.0f / norm;
+    for (uint32_t h = 0; h < m; h++) {
+      q[j][h] *= invNorm;
+    }
+    for (uint32_t i = 0; i < n; i++) {
+      r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m);
+    }
+  }
+
+  // Solve R B = Qt W Y to find B.  This is easy because R is upper triangular.
+  // We just work from bottom-right to top-left calculating B's coefficients.
+  float wy[M_ARRAY_LENGTH];
+  for (uint32_t h = 0; h < m; h++) {
+    wy[h] = y[h] * w[h];
+  }
+  for (uint32_t i = n; i-- != 0;) {
+    out_b[i] = VectorDot(&q[i][0], wy, m);
+    for (uint32_t j = n - 1; j > i; j--) {
+      out_b[i] -= r[i][j] * out_b[j];
+    }
+    out_b[i] /= r[i][i];
+  }
+
+  // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
+  // SSerr is the residual sum of squares (variance of the error),
+  // and SStot is the total sum of squares (variance of the data) where each
+  // has been weighted.
+  float ymean = 0;
+  for (uint32_t h = 0; h < m; h++) {
+    ymean += y[h];
+  }
+  ymean /= m;
+
+  float sserr = 0;
+  float sstot = 0;
+  for (uint32_t h = 0; h < m; h++) {
+    float err = y[h] - out_b[0];
+    float term = 1;
+    for (uint32_t i = 1; i < n; i++) {
+      term *= x[h];
+      err -= term * out_b[i];
+    }
+    sserr += w[h] * w[h] * err * err;
+    float var = y[h] - ymean;
+    sstot += w[h] * w[h] * var * var;
+  }
+  *out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
+  return true;
+}
+
+void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
+  BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value);
+  movements_[index_].id_bits = remaining_id_bits;
+}
+
+bool LeastSquaresVelocityTrackerStrategy::GetEstimator(
+    uint32_t id,
+    Estimator* out_estimator) const {
+  out_estimator->Clear();
+
+  // Iterate over movement samples in reverse time order and collect samples.
+  float x[HISTORY_SIZE];
+  float y[HISTORY_SIZE];
+  float w[HISTORY_SIZE];
+  float time[HISTORY_SIZE];
+  uint32_t m = 0;
+  uint32_t index = index_;
+  const base::TimeDelta horizon = base::TimeDelta::FromMilliseconds(HORIZON_MS);
+  const Movement& newest_movement = movements_[index_];
+  do {
+    const Movement& movement = movements_[index];
+    if (!movement.id_bits.has_bit(id))
+      break;
+
+    TimeDelta age = newest_movement.event_time - movement.event_time;
+    if (age > horizon)
+      break;
+
+    const Position& position = movement.GetPosition(id);
+    x[m] = position.x;
+    y[m] = position.y;
+    w[m] = ChooseWeight(index);
+    time[m] = -age.InSecondsF();
+    index = (index == 0 ? HISTORY_SIZE : index) - 1;
+  } while (++m < HISTORY_SIZE);
+
+  if (m == 0)
+    return false;  // no data
+
+  // Calculate a least squares polynomial fit.
+  uint32_t degree = degree_;
+  if (degree > m - 1)
+    degree = m - 1;
+
+  if (degree >= 1) {
+    float xdet, ydet;
+    uint32_t n = degree + 1;
+    if (SolveLeastSquares(time, x, w, m, n, out_estimator->xcoeff, &xdet) &&
+        SolveLeastSquares(time, y, w, m, n, out_estimator->ycoeff, &ydet)) {
+      out_estimator->time = newest_movement.event_time;
+      out_estimator->degree = degree;
+      out_estimator->confidence = xdet * ydet;
+      return true;
+    }
+  }
+
+  // No velocity data available for this pointer, but we do have its current
+  // position.
+  out_estimator->xcoeff[0] = x[0];
+  out_estimator->ycoeff[0] = y[0];
+  out_estimator->time = newest_movement.event_time;
+  out_estimator->degree = 0;
+  out_estimator->confidence = 1;
+  return true;
+}
+
+float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const {
+  switch (weighting_) {
+    case WEIGHTING_DELTA: {
+      // Weight points based on how much time elapsed between them and the next
+      // point so that points that "cover" a shorter time span are weighed less.
+      //   delta  0ms: 0.5
+      //   delta 10ms: 1.0
+      if (index == index_) {
+        return 1.0f;
+      }
+      uint32_t next_index = (index + 1) % HISTORY_SIZE;
+      float delta_millis =
+          static_cast<float>((movements_[next_index].event_time -
+                              movements_[index].event_time).InMillisecondsF());
+      if (delta_millis < 0)
+        return 0.5f;
+      if (delta_millis < 10)
+        return 0.5f + delta_millis * 0.05;
+
+      return 1.0f;
+    }
+
+    case WEIGHTING_CENTRAL: {
+      // Weight points based on their age, weighing very recent and very old
+      // points less.
+      //   age  0ms: 0.5
+      //   age 10ms: 1.0
+      //   age 50ms: 1.0
+      //   age 60ms: 0.5
+      float age_millis =
+          static_cast<float>((movements_[index_].event_time -
+                              movements_[index].event_time).InMillisecondsF());
+      if (age_millis < 0)
+        return 0.5f;
+      if (age_millis < 10)
+        return 0.5f + age_millis * 0.05;
+      if (age_millis < 50)
+        return 1.0f;
+      if (age_millis < 60)
+        return 0.5f + (60 - age_millis) * 0.05;
+
+      return 0.5f;
+    }
+
+    case WEIGHTING_RECENT: {
+      // Weight points based on their age, weighing older points less.
+      //   age   0ms: 1.0
+      //   age  50ms: 1.0
+      //   age 100ms: 0.5
+      float age_millis =
+          static_cast<float>((movements_[index_].event_time -
+                              movements_[index].event_time).InMillisecondsF());
+      if (age_millis < 50) {
+        return 1.0f;
+      }
+      if (age_millis < 100) {
+        return 0.5f + (100 - age_millis) * 0.01f;
+      }
+      return 0.5f;
+    }
+
+    case WEIGHTING_NONE:
+    default:
+      return 1.0f;
+  }
+}
+
+// --- IntegratingVelocityTrackerStrategy ---
+
+IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(
+    uint32_t degree)
+    : degree_(degree) {
+  DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE));
+}
+
+IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {}
+
+void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); }
+
+void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
+  pointer_id_bits_.value &= ~id_bits.value;
+}
+
+void IntegratingVelocityTrackerStrategy::AddMovement(
+    const TimeTicks& event_time,
+    BitSet32 id_bits,
+    const Position* positions) {
+  uint32_t index = 0;
+  for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) {
+    uint32_t id = iter_id_bits.clear_first_marked_bit();
+    State& state = mPointerState[id];
+    const Position& position = positions[index++];
+    if (pointer_id_bits_.has_bit(id))
+      UpdateState(state, event_time, position.x, position.y);
+    else
+      InitState(state, event_time, position.x, position.y);
+  }
+
+  pointer_id_bits_ = id_bits;
+}
+
+bool IntegratingVelocityTrackerStrategy::GetEstimator(
+    uint32_t id,
+    Estimator* out_estimator) const {
+  out_estimator->Clear();
+
+  if (pointer_id_bits_.has_bit(id)) {
+    const State& state = mPointerState[id];
+    PopulateEstimator(state, out_estimator);
+    return true;
+  }
+
+  return false;
+}
+
+void IntegratingVelocityTrackerStrategy::InitState(State& state,
+                                                   const TimeTicks& event_time,
+                                                   float xpos,
+                                                   float ypos) const {
+  state.update_time = event_time;
+  state.degree = 0;
+  state.xpos = xpos;
+  state.xvel = 0;
+  state.xaccel = 0;
+  state.ypos = ypos;
+  state.yvel = 0;
+  state.yaccel = 0;
+}
+
+void IntegratingVelocityTrackerStrategy::UpdateState(
+    State& state,
+    const TimeTicks& event_time,
+    float xpos,
+    float ypos) const {
+  const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2);
+  const float FILTER_TIME_CONSTANT = 0.010f;  // 10 milliseconds
+
+  if (event_time <= state.update_time + MIN_TIME_DELTA)
+    return;
+
+  float dt = static_cast<float>((event_time - state.update_time).InSecondsF());
+  state.update_time = event_time;
+
+  float xvel = (xpos - state.xpos) / dt;
+  float yvel = (ypos - state.ypos) / dt;
+  if (state.degree == 0) {
+    state.xvel = xvel;
+    state.yvel = yvel;
+    state.degree = 1;
+  } else {
+    float alpha = dt / (FILTER_TIME_CONSTANT + dt);
+    if (degree_ == 1) {
+      state.xvel += (xvel - state.xvel) * alpha;
+      state.yvel += (yvel - state.yvel) * alpha;
+    } else {
+      float xaccel = (xvel - state.xvel) / dt;
+      float yaccel = (yvel - state.yvel) / dt;
+      if (state.degree == 1) {
+        state.xaccel = xaccel;
+        state.yaccel = yaccel;
+        state.degree = 2;
+      } else {
+        state.xaccel += (xaccel - state.xaccel) * alpha;
+        state.yaccel += (yaccel - state.yaccel) * alpha;
+      }
+      state.xvel += (state.xaccel * dt) * alpha;
+      state.yvel += (state.yaccel * dt) * alpha;
+    }
+  }
+  state.xpos = xpos;
+  state.ypos = ypos;
+}
+
+void IntegratingVelocityTrackerStrategy::PopulateEstimator(
+    const State& state,
+    Estimator* out_estimator) const {
+  out_estimator->time = state.update_time;
+  out_estimator->confidence = 1.0f;
+  out_estimator->degree = state.degree;
+  out_estimator->xcoeff[0] = state.xpos;
+  out_estimator->xcoeff[1] = state.xvel;
+  out_estimator->xcoeff[2] = state.xaccel / 2;
+  out_estimator->ycoeff[0] = state.ypos;
+  out_estimator->ycoeff[1] = state.yvel;
+  out_estimator->ycoeff[2] = state.yaccel / 2;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/velocity_tracker.h b/ui/events/gesture_detection/velocity_tracker.h
new file mode 100644
index 0000000..8147695
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker.h
@@ -0,0 +1,150 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_H_
+#define UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/bitset_32.h"
+
+namespace ui {
+
+class MotionEvent;
+class VelocityTrackerStrategy;
+
+namespace {
+struct Estimator;
+struct Position;
+}
+
+// Port of VelocityTracker from Android
+// * platform/frameworks/native/include/input/VelocityTracker.h
+// * Change-Id: I4983db61b53e28479fc90d9211fafff68f7f49a6
+// * Please update the Change-Id as upstream Android changes are pulled.
+class VelocityTracker {
+ public:
+  enum {
+    // The maximum number of pointers to use when computing the velocity.
+    // Note that the supplied MotionEvent may expose more than 16 pointers, but
+    // at most |MAX_POINTERS| will be used.
+    MAX_POINTERS = 16,
+  };
+
+  enum Strategy {
+    // 1st order least squares.  Quality: POOR.
+    // Frequently underfits the touch data especially when the finger
+    // accelerates or changes direction.  Often underestimates velocity.  The
+    // direction is overly influenced by historical touch points.
+    LSQ1,
+
+    // 2nd order least squares.  Quality: VERY GOOD.
+    // Pretty much ideal, but can be confused by certain kinds of touch data,
+    // particularly if the panel has a tendency to generate delayed,
+    // duplicate or jittery touch coordinates when the finger is released.
+    LSQ2,
+
+    // 3rd order least squares.  Quality: UNUSABLE.
+    // Frequently overfits the touch data yielding wildly divergent estimates
+    // of the velocity when the finger is released.
+    LSQ3,
+
+    // 2nd order weighted least squares, delta weighting.
+    // Quality: EXPERIMENTAL
+    WLSQ2_DELTA,
+
+    // 2nd order weighted least squares, central weighting.
+    // Quality: EXPERIMENTAL
+    WLSQ2_CENTRAL,
+
+    // 2nd order weighted least squares, recent weighting.
+    // Quality: EXPERIMENTAL
+    WLSQ2_RECENT,
+
+    // 1st order integrating filter.  Quality: GOOD.
+    // Not as good as 'lsq2' because it cannot estimate acceleration but it is
+    // more tolerant of errors.  Like 'lsq1', this strategy tends to
+    // underestimate
+    // the velocity of a fling but this strategy tends to respond to changes in
+    // direction more quickly and accurately.
+    INT1,
+
+    // 2nd order integrating filter.  Quality: EXPERIMENTAL.
+    // For comparison purposes only.  Unlike 'int1' this strategy can compensate
+    // for acceleration but it typically overestimates the effect.
+    INT2,
+    STRATEGY_MAX = INT2,
+
+    // The default velocity tracker strategy.
+    // Although other strategies are available for testing and comparison
+    // purposes, this is the strategy that applications will actually use.  Be
+    // very careful when adjusting the default strategy because it can
+    // dramatically affect (often in a bad way) the user experience.
+    STRATEGY_DEFAULT = LSQ2,
+  };
+
+  // Creates a velocity tracker using the default strategy for the platform.
+  VelocityTracker();
+
+  // Creates a velocity tracker using the specified strategy.
+  // If strategy is NULL, uses the default strategy for the platform.
+  explicit VelocityTracker(Strategy strategy);
+
+  ~VelocityTracker();
+
+  // Resets the velocity tracker state.
+  void Clear();
+
+  // Adds movement information for all pointers in a MotionEvent, including
+  // historical samples.
+  void AddMovement(const MotionEvent& event);
+
+  // Gets the velocity of the specified pointer id in position units per second.
+  // Returns false and sets the velocity components to zero if there is
+  // insufficient movement information for the pointer.
+  bool GetVelocity(uint32_t id, float* outVx, float* outVy) const;
+
+  // Gets the active pointer id, or -1 if none.
+  inline int32_t GetActivePointerId() const { return active_pointer_id_; }
+
+  // Gets a bitset containing all pointer ids from the most recent movement.
+  inline BitSet32 GetCurrentPointerIdBits() const {
+    return current_pointer_id_bits_;
+  }
+
+ private:
+  // Resets the velocity tracker state for specific pointers.
+  // Call this method when some pointers have changed and may be reusing
+  // an id that was assigned to a different pointer earlier.
+  void ClearPointers(BitSet32 id_bits);
+
+  // Adds movement information for a set of pointers.
+  // The id_bits bitfield specifies the pointer ids of the pointers whose
+  // positions
+  // are included in the movement.
+  // The positions array contains position information for each pointer in order
+  // by
+  // increasing id.  Its size should be equal to the number of one bits in
+  // id_bits.
+  void AddMovement(const base::TimeTicks& event_time,
+                   BitSet32 id_bits,
+                   const Position* positions);
+
+  // Gets an estimator for the recent movements of the specified pointer id.
+  // Returns false and clears the estimator if there is no information available
+  // about the pointer.
+  bool GetEstimator(uint32_t id, Estimator* out_estimator) const;
+
+  base::TimeTicks last_event_time_;
+  BitSet32 current_pointer_id_bits_;
+  int32_t active_pointer_id_;
+  scoped_ptr<VelocityTrackerStrategy> strategy_;
+
+  DISALLOW_COPY_AND_ASSIGN(VelocityTracker);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_H_
diff --git a/ui/events/gesture_detection/velocity_tracker_state.cc b/ui/events/gesture_detection/velocity_tracker_state.cc
new file mode 100644
index 0000000..1216856
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker_state.cc
@@ -0,0 +1,105 @@
+// Copyright 2014 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 "ui/events/gesture_detection/velocity_tracker_state.h"
+
+#include "base/logging.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+namespace {
+// Special constant to request the velocity of the active pointer.
+const int ACTIVE_POINTER_ID = -1;
+}
+
+VelocityTrackerState::VelocityTrackerState()
+    : velocity_tracker_(VelocityTracker::STRATEGY_DEFAULT),
+      active_pointer_id_(ACTIVE_POINTER_ID) {}
+
+VelocityTrackerState::VelocityTrackerState(VelocityTracker::Strategy strategy)
+    : velocity_tracker_(strategy), active_pointer_id_(ACTIVE_POINTER_ID) {}
+
+VelocityTrackerState::~VelocityTrackerState() {}
+
+void VelocityTrackerState::Clear() {
+  velocity_tracker_.Clear();
+  active_pointer_id_ = ACTIVE_POINTER_ID;
+  calculated_id_bits_.clear();
+}
+
+void VelocityTrackerState::AddMovement(const MotionEvent& event) {
+  velocity_tracker_.AddMovement(event);
+}
+
+void VelocityTrackerState::ComputeCurrentVelocity(int32_t units,
+                                                  float max_velocity) {
+  DCHECK_GE(max_velocity, 0);
+
+  BitSet32 id_bits(velocity_tracker_.GetCurrentPointerIdBits());
+  calculated_id_bits_ = id_bits;
+
+  for (uint32_t index = 0; !id_bits.is_empty(); index++) {
+    uint32_t id = id_bits.clear_first_marked_bit();
+
+    float vx, vy;
+    velocity_tracker_.GetVelocity(id, &vx, &vy);
+
+    vx = vx * units / 1000.f;
+    vy = vy * units / 1000.f;
+
+    if (vx > max_velocity)
+      vx = max_velocity;
+    else if (vx < -max_velocity)
+      vx = -max_velocity;
+
+    if (vy > max_velocity)
+      vy = max_velocity;
+    else if (vy < -max_velocity)
+      vy = -max_velocity;
+
+    Velocity& velocity = calculated_velocity_[index];
+    velocity.vx = vx;
+    velocity.vy = vy;
+  }
+}
+
+float VelocityTrackerState::GetXVelocity(int32_t id) const {
+  float vx;
+  GetVelocity(id, &vx, NULL);
+  return vx;
+}
+
+float VelocityTrackerState::GetYVelocity(int32_t id) const {
+  float vy;
+  GetVelocity(id, NULL, &vy);
+  return vy;
+}
+
+void VelocityTrackerState::GetVelocity(int32_t id,
+                                       float* out_vx,
+                                       float* out_vy) const {
+  DCHECK(out_vx || out_vy);
+  if (id == ACTIVE_POINTER_ID)
+    id = velocity_tracker_.GetActivePointerId();
+
+  float vx, vy;
+  if (id >= 0 && id <= MotionEvent::MAX_POINTER_ID &&
+      calculated_id_bits_.has_bit(id)) {
+    uint32_t index = calculated_id_bits_.get_index_of_bit(id);
+    const Velocity& velocity = calculated_velocity_[index];
+    vx = velocity.vx;
+    vy = velocity.vy;
+  } else {
+    vx = 0;
+    vy = 0;
+  }
+
+  if (out_vx)
+    *out_vx = vx;
+
+  if (out_vy)
+    *out_vy = vy;
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_detection/velocity_tracker_state.h b/ui/events/gesture_detection/velocity_tracker_state.h
new file mode 100644
index 0000000..780871c
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker_state.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_STATE_H_
+#define UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_STATE_H_
+
+#include "base/basictypes.h"
+#include "ui/events/gesture_detection/bitset_32.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/velocity_tracker.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// Port of VelocityTrackerState from Android
+// * platform/frameworks/base/core/jni/android_view_VelocityTracker.cpp
+// * Change-Id: I3517881b87b47dcc209d80dbd0ac6b5cf29a766f
+// * Please update the Change-Id as upstream Android changes are pulled.
+class GESTURE_DETECTION_EXPORT VelocityTrackerState {
+ public:
+  VelocityTrackerState();
+  explicit VelocityTrackerState(VelocityTracker::Strategy strategy);
+  ~VelocityTrackerState();
+
+  void Clear();
+  void AddMovement(const MotionEvent& event);
+  void ComputeCurrentVelocity(int32_t units, float max_velocity);
+  float GetXVelocity(int32_t id) const;
+  float GetYVelocity(int32_t id) const;
+
+ private:
+  struct Velocity {
+    float vx, vy;
+  };
+
+  void GetVelocity(int32_t id, float* out_vx, float* out_vy) const;
+
+  VelocityTracker velocity_tracker_;
+  int32_t active_pointer_id_;
+  BitSet32 calculated_id_bits_;
+  Velocity calculated_velocity_[VelocityTracker::MAX_POINTERS];
+
+  DISALLOW_COPY_AND_ASSIGN(VelocityTrackerState);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_VELOCITY_TRACKER_STATE_H_
diff --git a/ui/events/gesture_detection/velocity_tracker_unittest.cc b/ui/events/gesture_detection/velocity_tracker_unittest.cc
new file mode 100644
index 0000000..9b45053
--- /dev/null
+++ b/ui/events/gesture_detection/velocity_tracker_unittest.cc
@@ -0,0 +1,222 @@
+// Copyright 2014 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 "base/basictypes.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/velocity_tracker_state.h"
+#include "ui/events/test/mock_motion_event.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+using ui::test::MockMotionEvent;
+
+namespace ui {
+namespace {
+
+const TimeDelta kTenMillis = TimeDelta::FromMilliseconds(10);
+const TimeDelta kOneSecond = TimeDelta::FromSeconds(1);
+const float kEpsilson = .01f;
+
+const char* GetStrategyName(VelocityTracker::Strategy strategy) {
+  switch (strategy) {
+    case VelocityTracker::LSQ1: return "LSQ1";
+    case VelocityTracker::LSQ2: return "LSQ2";
+    case VelocityTracker::LSQ3: return "LSQ3";
+    case VelocityTracker::WLSQ2_DELTA: return "WLSQ2_DELTA";
+    case VelocityTracker::WLSQ2_CENTRAL: return "WLSQ2_CENTRAL";
+    case VelocityTracker::WLSQ2_RECENT: return "WLSQ2_RECENT";
+    case VelocityTracker::INT1: return "INT1";
+    case VelocityTracker::INT2: return "INT2";
+  };
+  NOTREACHED() << "Invalid strategy";
+  return "";
+}
+
+}  // namespace
+
+class VelocityTrackerTest : public testing::Test {
+ public:
+  VelocityTrackerTest() {}
+  virtual ~VelocityTrackerTest() {}
+
+ protected:
+  static MockMotionEvent Sample(MotionEvent::Action action,
+                                gfx::PointF p0,
+                                TimeTicks t0,
+                                gfx::Vector2dF v,
+                                TimeDelta dt) {
+    const gfx::PointF p = p0 + ScaleVector2d(v, dt.InSecondsF());
+    return MockMotionEvent(action, t0 + dt, p.x(), p.y());
+  }
+
+  static void ApplyMovementSequence(VelocityTrackerState* state,
+                                    gfx::PointF p0,
+                                    gfx::Vector2dF v,
+                                    TimeTicks t0,
+                                    TimeDelta t,
+                                    size_t samples) {
+    EXPECT_TRUE(!!samples);
+    if (!samples)
+      return;
+    const base::TimeDelta dt = t / samples;
+    state->AddMovement(Sample(MotionEvent::ACTION_DOWN, p0, t0, v, dt * 0));
+    ApplyMovement(state, p0, v, t0, t, samples);
+    state->AddMovement(Sample(MotionEvent::ACTION_UP, p0, t0, v, t));
+  }
+
+  static void ApplyMovement(VelocityTrackerState* state,
+                            gfx::PointF p0,
+                            gfx::Vector2dF v,
+                            TimeTicks t0,
+                            TimeDelta t,
+                            size_t samples) {
+    EXPECT_TRUE(!!samples);
+    if (!samples)
+      return;
+    const base::TimeDelta dt = t / samples;
+    for (size_t i = 0; i < samples; ++i)
+      state->AddMovement(Sample(MotionEvent::ACTION_MOVE, p0, t0, v, dt * i));
+  }
+};
+
+TEST_F(VelocityTrackerTest, Basic) {
+  const gfx::PointF p0(0, 0);
+  const gfx::Vector2dF v(0, 500);
+  const size_t samples = 60;
+
+  for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) {
+    VelocityTracker::Strategy strategy =
+        static_cast<VelocityTracker::Strategy>(i);
+
+    SCOPED_TRACE(GetStrategyName(strategy));
+    VelocityTrackerState state(strategy);
+
+    // Default state should report zero velocity.
+    EXPECT_EQ(0, state.GetXVelocity(0));
+    EXPECT_EQ(0, state.GetYVelocity(0));
+
+    // Sample a constant velocity sequence.
+    ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), kOneSecond, samples);
+
+    // The computed velocity should match that of the input.
+    state.ComputeCurrentVelocity(1000, 20000);
+    EXPECT_NEAR(v.x(), state.GetXVelocity(0), kEpsilson * v.x());
+    EXPECT_NEAR(v.y(), state.GetYVelocity(0), kEpsilson * v.y());
+
+    // A pointer ID of -1 should report the velocity of the active pointer.
+    EXPECT_NEAR(v.x(), state.GetXVelocity(-1), kEpsilson * v.x());
+    EXPECT_NEAR(v.y(), state.GetYVelocity(-1), kEpsilson * v.y());
+
+    // Invalid pointer ID's should report zero velocity.
+    EXPECT_EQ(0, state.GetXVelocity(1));
+    EXPECT_EQ(0, state.GetYVelocity(1));
+    EXPECT_EQ(0, state.GetXVelocity(7));
+    EXPECT_EQ(0, state.GetYVelocity(7));
+  }
+}
+
+TEST_F(VelocityTrackerTest, MaxVelocity) {
+  const gfx::PointF p0(0, 0);
+  const gfx::Vector2dF v(-50000, 50000);
+  const size_t samples = 3;
+  const base::TimeDelta dt = kTenMillis * 2;
+
+  VelocityTrackerState state;
+  ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), dt, samples);
+
+  // The computed velocity should be restricted to the provided maximum.
+  state.ComputeCurrentVelocity(1000, 100);
+  EXPECT_NEAR(-100, state.GetXVelocity(0), kEpsilson);
+  EXPECT_NEAR(100, state.GetYVelocity(0), kEpsilson);
+
+  state.ComputeCurrentVelocity(1000, 1000);
+  EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
+  EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
+}
+
+TEST_F(VelocityTrackerTest, VaryingVelocity) {
+  const gfx::PointF p0(0, 0);
+  const gfx::Vector2dF vFast(0, 500);
+  const gfx::Vector2dF vSlow = ScaleVector2d(vFast, 0.5f);
+  const size_t samples = 12;
+
+  for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) {
+    VelocityTracker::Strategy strategy =
+        static_cast<VelocityTracker::Strategy>(i);
+
+    SCOPED_TRACE(GetStrategyName(strategy));
+    VelocityTrackerState state(strategy);
+
+    base::TimeTicks t0 = base::TimeTicks::Now();
+    base::TimeDelta dt = kTenMillis * 10;
+    state.AddMovement(
+        Sample(MotionEvent::ACTION_DOWN, p0, t0, vFast, base::TimeDelta()));
+
+    // Apply some fast movement and compute the velocity.
+    gfx::PointF pCurr = p0;
+    base::TimeTicks tCurr = t0;
+    ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples);
+    state.ComputeCurrentVelocity(1000, 20000);
+    float vOldY = state.GetYVelocity(0);
+
+    // Apply some slow movement.
+    pCurr += ScaleVector2d(vFast, dt.InSecondsF());
+    tCurr += dt;
+    ApplyMovement(&state, pCurr, vSlow, tCurr, dt, samples);
+
+    // The computed velocity should have decreased.
+    state.ComputeCurrentVelocity(1000, 20000);
+    float vCurrentY = state.GetYVelocity(0);
+    EXPECT_GT(vFast.y(), vCurrentY);
+    EXPECT_GT(vOldY, vCurrentY);
+    vOldY = vCurrentY;
+
+    // Apply some additional fast movement.
+    pCurr += ScaleVector2d(vSlow, dt.InSecondsF());
+    tCurr += dt;
+    ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples);
+
+    // The computed velocity should have increased.
+    state.ComputeCurrentVelocity(1000, 20000);
+    vCurrentY = state.GetYVelocity(0);
+    EXPECT_LT(vSlow.y(), vCurrentY);
+    EXPECT_LT(vOldY, vCurrentY);
+  }
+}
+
+TEST_F(VelocityTrackerTest, DelayedActionUp) {
+  const gfx::PointF p0(0, 0);
+  const gfx::Vector2dF v(-50000, 50000);
+  const size_t samples = 10;
+  const base::TimeTicks t0 = base::TimeTicks::Now();
+  const base::TimeDelta dt = kTenMillis * 2;
+
+  VelocityTrackerState state;
+  state.AddMovement(
+      Sample(MotionEvent::ACTION_DOWN, p0, t0, v, base::TimeDelta()));
+
+  // Apply the movement and verify a (non-zero) velocity.
+  ApplyMovement(&state, p0, v, t0, dt, samples);
+  state.ComputeCurrentVelocity(1000, 1000);
+  EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
+  EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
+
+  // Apply the delayed ACTION_UP.
+  const gfx::PointF p1 = p0 + ScaleVector2d(v, dt.InSecondsF());
+  const base::TimeTicks t1 = t0 + dt + kTenMillis * 10;
+  state.AddMovement(Sample(
+      MotionEvent::ACTION_UP, p1, t1, v, base::TimeDelta()));
+
+  // The tracked velocity should have been reset.
+  state.ComputeCurrentVelocity(1000, 1000);
+  EXPECT_EQ(0.f, state.GetXVelocity(0));
+  EXPECT_EQ(0.f, state.GetYVelocity(0));
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_event_details.cc b/ui/events/gesture_event_details.cc
new file mode 100644
index 0000000..7807131
--- /dev/null
+++ b/ui/events/gesture_event_details.cc
@@ -0,0 +1,62 @@
+// Copyright 2014 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 "ui/events/gesture_event_details.h"
+
+namespace ui {
+
+GestureEventDetails::GestureEventDetails()
+    : type_(ET_UNKNOWN), touch_points_(0), oldest_touch_id_(-1) {
+}
+
+GestureEventDetails::GestureEventDetails(ui::EventType type)
+    : type_(type), touch_points_(1), oldest_touch_id_(-1) {
+  DCHECK_GE(type, ET_GESTURE_TYPE_START);
+  DCHECK_LE(type, ET_GESTURE_TYPE_END);
+}
+
+GestureEventDetails::GestureEventDetails(ui::EventType type,
+                                         float delta_x,
+                                         float delta_y)
+    : type_(type), touch_points_(1), oldest_touch_id_(-1) {
+  DCHECK_GE(type, ET_GESTURE_TYPE_START);
+  DCHECK_LE(type, ET_GESTURE_TYPE_END);
+  switch (type_) {
+    case ui::ET_GESTURE_SCROLL_BEGIN:
+      data.scroll_begin.x_hint = delta_x;
+      data.scroll_begin.y_hint = delta_y;
+      break;
+
+    case ui::ET_GESTURE_SCROLL_UPDATE:
+      data.scroll_update.x = delta_x;
+      data.scroll_update.y = delta_y;
+      break;
+
+    case ui::ET_SCROLL_FLING_START:
+      data.fling_velocity.x = delta_x;
+      data.fling_velocity.y = delta_y;
+      break;
+
+    case ui::ET_GESTURE_TWO_FINGER_TAP:
+      data.first_finger_enclosing_rectangle.width = delta_x;
+      data.first_finger_enclosing_rectangle.height = delta_y;
+      break;
+
+    case ui::ET_GESTURE_SWIPE:
+      data.swipe.left = delta_x < 0;
+      data.swipe.right = delta_x > 0;
+      data.swipe.up = delta_y < 0;
+      data.swipe.down = delta_y > 0;
+      break;
+
+    default:
+      NOTREACHED() << "Invalid event type for constructor: " << type;
+  }
+}
+
+GestureEventDetails::Details::Details() {
+  memset(this, 0, sizeof(Details));
+}
+
+}  // namespace ui
diff --git a/ui/events/gesture_event_details.h b/ui/events/gesture_event_details.h
new file mode 100644
index 0000000..da7a60b
--- /dev/null
+++ b/ui/events/gesture_event_details.h
@@ -0,0 +1,187 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_
+
+#include "base/logging.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/rect_conversions.h"
+
+namespace ui {
+
+struct EVENTS_BASE_EXPORT GestureEventDetails {
+ public:
+  GestureEventDetails();
+  explicit GestureEventDetails(EventType type);
+  GestureEventDetails(EventType type, float delta_x, float delta_y);
+
+  EventType type() const { return type_; }
+
+  int touch_points() const { return touch_points_; }
+  void set_touch_points(int touch_points) {
+    DCHECK_GT(touch_points, 0);
+    touch_points_ = touch_points;
+  }
+
+  int oldest_touch_id() const { return oldest_touch_id_; }
+  void set_oldest_touch_id(int oldest_touch_id) {
+    DCHECK_GE(oldest_touch_id, 0);
+    oldest_touch_id_ = oldest_touch_id;
+  }
+
+  // TODO(tdresser): Return RectF. See crbug.com/337824.
+  const gfx::Rect bounding_box() const {
+    return ToEnclosingRect(bounding_box_);
+  }
+
+  const gfx::RectF& bounding_box_f() const {
+    return bounding_box_;
+  }
+
+  void set_bounding_box(const gfx::RectF& box) { bounding_box_ = box; }
+
+  float scroll_x_hint() const {
+    DCHECK_EQ(ET_GESTURE_SCROLL_BEGIN, type_);
+    return data.scroll_begin.x_hint;
+  }
+
+  float scroll_y_hint() const {
+    DCHECK_EQ(ET_GESTURE_SCROLL_BEGIN, type_);
+    return data.scroll_begin.y_hint;
+  }
+
+  float scroll_x() const {
+    DCHECK_EQ(ET_GESTURE_SCROLL_UPDATE, type_);
+    return data.scroll_update.x;
+  }
+
+  float scroll_y() const {
+    DCHECK_EQ(ET_GESTURE_SCROLL_UPDATE, type_);
+    return data.scroll_update.y;
+  }
+
+  float velocity_x() const {
+    DCHECK_EQ(ET_SCROLL_FLING_START, type_);
+    return data.fling_velocity.x;
+  }
+
+  float velocity_y() const {
+    DCHECK_EQ(ET_SCROLL_FLING_START, type_);
+    return data.fling_velocity.y;
+  }
+
+  float first_finger_width() const {
+    DCHECK_EQ(ET_GESTURE_TWO_FINGER_TAP, type_);
+    return data.first_finger_enclosing_rectangle.width;
+  }
+
+  float first_finger_height() const {
+    DCHECK_EQ(ET_GESTURE_TWO_FINGER_TAP, type_);
+    return data.first_finger_enclosing_rectangle.height;
+  }
+
+  float scale() const {
+    DCHECK_EQ(ET_GESTURE_PINCH_UPDATE, type_);
+    return data.scale;
+  }
+
+  bool swipe_left() const {
+    DCHECK_EQ(ET_GESTURE_SWIPE, type_);
+    return data.swipe.left;
+  }
+
+  bool swipe_right() const {
+    DCHECK_EQ(ET_GESTURE_SWIPE, type_);
+    return data.swipe.right;
+  }
+
+  bool swipe_up() const {
+    DCHECK_EQ(ET_GESTURE_SWIPE, type_);
+    return data.swipe.up;
+  }
+
+  bool swipe_down() const {
+    DCHECK_EQ(ET_GESTURE_SWIPE, type_);
+    return data.swipe.down;
+  }
+
+  int tap_count() const {
+    DCHECK(type_ == ET_GESTURE_TAP ||
+           type_ == ET_GESTURE_TAP_UNCONFIRMED ||
+           type_ == ET_GESTURE_DOUBLE_TAP);
+    return data.tap_count;
+  }
+
+  void set_tap_count(int tap_count) {
+    DCHECK_GE(tap_count, 0);
+    DCHECK(type_ == ET_GESTURE_TAP ||
+           type_ == ET_GESTURE_TAP_UNCONFIRMED ||
+           type_ == ET_GESTURE_DOUBLE_TAP);
+    data.tap_count = tap_count;
+  }
+
+  void set_scale(float scale) {
+    DCHECK_GE(scale, 0.0f);
+    DCHECK_EQ(type_, ET_GESTURE_PINCH_UPDATE);
+    data.scale = scale;
+  }
+
+ private:
+  EventType type_;
+  union Details {
+    Details();
+    struct {  // SCROLL start details.
+      // Distance that caused the scroll to start.  Generally redundant with
+      // the x/y values from the first scroll_update.
+      float x_hint;
+      float y_hint;
+    } scroll_begin;
+
+    struct {  // SCROLL delta.
+      float x;
+      float y;
+    } scroll_update;
+
+    float scale;  // PINCH scale.
+
+    struct {  // FLING velocity.
+      float x;
+      float y;
+    } fling_velocity;
+
+    // Dimensions of the first finger's enclosing rectangle for
+    // TWO_FINGER_TAP.
+    struct {
+      float width;
+      float height;
+    } first_finger_enclosing_rectangle;
+
+    struct {  // SWIPE direction.
+      bool left;
+      bool right;
+      bool up;
+      bool down;
+    } swipe;
+
+    // Tap information must be set for ET_GESTURE_TAP,
+    // ET_GESTURE_TAP_UNCONFIRMED, and ET_GESTURE_DOUBLE_TAP events.
+    int tap_count;  // TAP repeat count.
+  } data;
+
+  int touch_points_;  // Number of active touch points in the gesture.
+
+  // Bounding box is an axis-aligned rectangle that contains all the
+  // enclosing rectangles of the touch-points in the gesture.
+  gfx::RectF bounding_box_;
+
+  // The touch id of the oldest touch contributing to the gesture.
+  int oldest_touch_id_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_
diff --git a/ui/events/gestures/OWNERS b/ui/events/gestures/OWNERS
new file mode 100644
index 0000000..92704b0
--- /dev/null
+++ b/ui/events/gestures/OWNERS
@@ -0,0 +1,3 @@
+rjkroege@chromium.org
+sadrul@chromium.org
+tdresser@chromium.org
diff --git a/ui/events/gestures/fling_curve.cc b/ui/events/gestures/fling_curve.cc
new file mode 100644
index 0000000..6f63019
--- /dev/null
+++ b/ui/events/gestures/fling_curve.cc
@@ -0,0 +1,80 @@
+// Copyright 2014 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 "ui/events/gestures/fling_curve.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "base/logging.h"
+
+namespace {
+
+const float kDefaultAlpha = -5.70762e+03f;
+const float kDefaultBeta = 1.72e+02f;
+const float kDefaultGamma = 3.7e+00f;
+
+inline double GetPositionAtTime(double t) {
+  return kDefaultAlpha * exp(-kDefaultGamma * t) - kDefaultBeta * t -
+         kDefaultAlpha;
+}
+
+inline double GetVelocityAtTime(double t) {
+  return -kDefaultAlpha * kDefaultGamma * exp(-kDefaultGamma * t) -
+         kDefaultBeta;
+}
+
+inline double GetTimeAtVelocity(double v) {
+  return -log((v + kDefaultBeta) / (-kDefaultAlpha * kDefaultGamma)) /
+         kDefaultGamma;
+}
+
+}  // namespace
+
+namespace ui {
+
+FlingCurve::FlingCurve(const gfx::Vector2dF& velocity,
+                       base::TimeTicks start_timestamp)
+    : curve_duration_(GetTimeAtVelocity(0)),
+      start_timestamp_(start_timestamp),
+      time_offset_(0),
+      position_offset_(0) {
+  float max_start_velocity = std::max(fabs(velocity.x()), fabs(velocity.y()));
+  if (max_start_velocity > GetVelocityAtTime(0))
+    max_start_velocity = GetVelocityAtTime(0);
+  CHECK_GT(max_start_velocity, 0);
+
+  displacement_ratio_ = gfx::Vector2dF(velocity.x() / max_start_velocity,
+                                       velocity.y() / max_start_velocity);
+  time_offset_ = GetTimeAtVelocity(max_start_velocity);
+  position_offset_ = GetPositionAtTime(time_offset_);
+  last_timestamp_ = start_timestamp_ + base::TimeDelta::FromSecondsD(
+                                           curve_duration_ - time_offset_);
+}
+
+FlingCurve::~FlingCurve() {
+}
+
+gfx::Vector2dF FlingCurve::GetScrollAmountAtTime(base::TimeTicks current) {
+  if (current < start_timestamp_)
+    return gfx::Vector2dF();
+
+  float displacement = 0;
+  if (current < last_timestamp_) {
+    float time = time_offset_ + (current - start_timestamp_).InSecondsF();
+    CHECK_LT(time, curve_duration_);
+    displacement = GetPositionAtTime(time) - position_offset_;
+  } else {
+    displacement = GetPositionAtTime(curve_duration_) - position_offset_;
+  }
+
+  gfx::Vector2dF scroll(displacement * displacement_ratio_.x(),
+                        displacement * displacement_ratio_.y());
+  gfx::Vector2dF scroll_increment(scroll.x() - cumulative_scroll_.x(),
+                                  scroll.y() - cumulative_scroll_.y());
+  cumulative_scroll_ = scroll;
+  return scroll_increment;
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/fling_curve.h b/ui/events/gestures/fling_curve.h
new file mode 100644
index 0000000..583e172
--- /dev/null
+++ b/ui/events/gestures/fling_curve.h
@@ -0,0 +1,40 @@
+// Copyright 2014 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 UI_EVENTS_GESTURES_FLING_CURVE_H_
+#define UI_EVENTS_GESTURES_FLING_CURVE_H_
+
+#include "base/time/time.h"
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+namespace ui {
+
+// FlingCurve can be used to scroll a UI element suitable for touch screen-based
+// flings.
+class EVENTS_BASE_EXPORT FlingCurve {
+ public:
+  FlingCurve(const gfx::Vector2dF& velocity, base::TimeTicks start_timestamp);
+  ~FlingCurve();
+
+  gfx::Vector2dF GetScrollAmountAtTime(base::TimeTicks current_timestamp);
+  base::TimeTicks start_timestamp() const { return start_timestamp_; }
+
+ private:
+  const float curve_duration_;
+  const base::TimeTicks start_timestamp_;
+
+  gfx::Vector2dF displacement_ratio_;
+  gfx::Vector2dF cumulative_scroll_;
+  base::TimeTicks last_timestamp_;
+  float time_offset_;
+  float position_offset_;
+
+  DISALLOW_COPY_AND_ASSIGN(FlingCurve);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURES_FLING_CURVE_H_
diff --git a/ui/events/gestures/fling_curve_unittest.cc b/ui/events/gestures/fling_curve_unittest.cc
new file mode 100644
index 0000000..653a5ef
--- /dev/null
+++ b/ui/events/gestures/fling_curve_unittest.cc
@@ -0,0 +1,36 @@
+// Copyright 2014 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 "ui/events/gestures/fling_curve.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/frame_time.h"
+
+namespace ui {
+
+TEST(FlingCurveTest, Basic) {
+  const gfx::Vector2dF velocity(0, 5000);
+  base::TimeTicks now = gfx::FrameTime::Now();
+  FlingCurve curve(velocity, now);
+
+  gfx::Vector2dF scroll =
+      curve.GetScrollAmountAtTime(now + base::TimeDelta::FromMilliseconds(20));
+  EXPECT_EQ(0, scroll.x());
+  EXPECT_NEAR(scroll.y(), 96, 1);
+
+  scroll =
+      curve.GetScrollAmountAtTime(now + base::TimeDelta::FromMilliseconds(250));
+  EXPECT_EQ(0, scroll.x());
+  EXPECT_NEAR(scroll.y(), 705, 1);
+
+  scroll =
+      curve.GetScrollAmountAtTime(now + base::TimeDelta::FromSeconds(10));
+  EXPECT_EQ(0, scroll.x());
+  EXPECT_NEAR(scroll.y(), 392, 1);
+
+  EXPECT_TRUE(curve.GetScrollAmountAtTime(
+      now + base::TimeDelta::FromSeconds(20)).IsZero());
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_configuration.cc b/ui/events/gestures/gesture_configuration.cc
new file mode 100644
index 0000000..1ce13d7
--- /dev/null
+++ b/ui/events/gestures/gesture_configuration.cc
@@ -0,0 +1,43 @@
+// Copyright (c) 2013 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 "ui/events/gestures/gesture_configuration.h"
+
+namespace ui {
+
+int GestureConfiguration::default_radius_ = 25;
+int GestureConfiguration::fling_max_cancel_to_down_time_in_ms_ = 400;
+int GestureConfiguration::fling_max_tap_gap_time_in_ms_ = 200;
+float GestureConfiguration::fling_velocity_cap_ = 17000.0f;
+int GestureConfiguration::tab_scrub_activation_delay_in_ms_ = 200;
+double GestureConfiguration::long_press_time_in_seconds_ = 1.0;
+double GestureConfiguration::semi_long_press_time_in_seconds_ = 0.4;
+double GestureConfiguration::max_distance_for_two_finger_tap_in_pixels_ = 300;
+double GestureConfiguration::max_seconds_between_double_click_ = 0.7;
+double
+  GestureConfiguration::max_separation_for_gesture_touches_in_pixels_ = 150;
+float GestureConfiguration::max_swipe_deviation_angle_ = 20;
+double
+  GestureConfiguration::max_touch_down_duration_in_seconds_for_click_ = 0.8;
+double GestureConfiguration::max_touch_move_in_pixels_for_click_ = 15;
+double GestureConfiguration::max_distance_between_taps_for_double_tap_ = 20;
+double GestureConfiguration::min_distance_for_pinch_scroll_in_pixels_ = 20;
+double GestureConfiguration::min_pinch_update_distance_in_pixels_ = 5;
+float GestureConfiguration::min_scroll_velocity_ = 30.0f;
+double GestureConfiguration::min_swipe_speed_ = 20;
+
+// If this is too small, we currently can get single finger pinch zoom. See
+// crbug.com/357237 for details.
+int GestureConfiguration::min_scaling_span_in_pixels_ = 125;
+int GestureConfiguration::show_press_delay_in_ms_ = 150;
+
+// TODO(jdduke): Disable and remove entirely when issues with intermittent
+// scroll end detection on the Pixel are resolved, crbug.com/353702.
+#if defined(OS_CHROMEOS)
+int GestureConfiguration::scroll_debounce_interval_in_ms_ = 30;
+#else
+int GestureConfiguration::scroll_debounce_interval_in_ms_ = 0;
+#endif
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_configuration.h b/ui/events/gestures/gesture_configuration.h
new file mode 100644
index 0000000..1537b8e
--- /dev/null
+++ b/ui/events/gestures/gesture_configuration.h
@@ -0,0 +1,194 @@
+// Copyright (c) 2013 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 UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_
+#define UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+
+namespace ui {
+
+// TODO: Expand this design to support multiple OS configuration
+// approaches (windows, chrome, others).  This would turn into an
+// abstract base class.
+
+class EVENTS_BASE_EXPORT GestureConfiguration {
+ public:
+  // Ordered alphabetically ignoring underscores, to align with the
+  // associated list of prefs in gesture_prefs_aura.cc.
+  static int default_radius() {
+    return default_radius_;
+  }
+  static void set_default_radius(int radius) { default_radius_ = radius; }
+  static int fling_max_cancel_to_down_time_in_ms() {
+    return fling_max_cancel_to_down_time_in_ms_;
+  }
+  static void set_fling_max_cancel_to_down_time_in_ms(int val) {
+    fling_max_cancel_to_down_time_in_ms_ = val;
+  }
+  static int fling_max_tap_gap_time_in_ms() {
+    return fling_max_tap_gap_time_in_ms_;
+  }
+  static void set_fling_max_tap_gap_time_in_ms(int val) {
+    fling_max_tap_gap_time_in_ms_ = val;
+  }
+  static double long_press_time_in_seconds() {
+    return long_press_time_in_seconds_;
+  }
+  static double semi_long_press_time_in_seconds() {
+    return semi_long_press_time_in_seconds_;
+  }
+  static double max_distance_for_two_finger_tap_in_pixels() {
+    return max_distance_for_two_finger_tap_in_pixels_;
+  }
+  static void set_max_distance_for_two_finger_tap_in_pixels(double val) {
+    max_distance_for_two_finger_tap_in_pixels_ = val;
+  }
+  static void set_long_press_time_in_seconds(double val) {
+    long_press_time_in_seconds_ = val;
+  }
+  static void set_semi_long_press_time_in_seconds(double val) {
+    semi_long_press_time_in_seconds_ = val;
+  }
+  static double max_seconds_between_double_click() {
+    return max_seconds_between_double_click_;
+  }
+  static void set_max_seconds_between_double_click(double val) {
+    max_seconds_between_double_click_ = val;
+  }
+  static int max_separation_for_gesture_touches_in_pixels() {
+    return max_separation_for_gesture_touches_in_pixels_;
+  }
+  static void set_max_separation_for_gesture_touches_in_pixels(int val) {
+    max_separation_for_gesture_touches_in_pixels_ = val;
+  }
+  static float max_swipe_deviation_angle() {
+    return max_swipe_deviation_angle_;
+  }
+  static void set_max_swipe_deviation_angle(float val) {
+    max_swipe_deviation_angle_ = val;
+  }
+  static double max_touch_down_duration_in_seconds_for_click() {
+    return max_touch_down_duration_in_seconds_for_click_;
+  }
+  static void set_max_touch_down_duration_in_seconds_for_click(double val) {
+    max_touch_down_duration_in_seconds_for_click_ = val;
+  }
+  static double max_touch_move_in_pixels_for_click() {
+    return max_touch_move_in_pixels_for_click_;
+  }
+  static void set_max_touch_move_in_pixels_for_click(double val) {
+    max_touch_move_in_pixels_for_click_ = val;
+  }
+  static double max_distance_between_taps_for_double_tap() {
+    return max_distance_between_taps_for_double_tap_;
+  }
+  static void set_max_distance_between_taps_for_double_tap(double val) {
+    max_distance_between_taps_for_double_tap_ = val;
+  }
+  static double min_distance_for_pinch_scroll_in_pixels() {
+    return min_distance_for_pinch_scroll_in_pixels_;
+  }
+  static void set_min_distance_for_pinch_scroll_in_pixels(double val) {
+    min_distance_for_pinch_scroll_in_pixels_ = val;
+  }
+  static double min_pinch_update_distance_in_pixels() {
+    return min_pinch_update_distance_in_pixels_;
+  }
+  static void set_min_pinch_update_distance_in_pixels(double val) {
+    min_pinch_update_distance_in_pixels_ = val;
+  }
+  static float min_scroll_velocity() {
+    return min_scroll_velocity_;
+  }
+  static void set_min_scroll_velocity(float val) {
+    min_scroll_velocity_ = val;
+  }
+  static double min_swipe_speed() {
+    return min_swipe_speed_;
+  }
+  static void set_min_swipe_speed(double val) {
+    min_swipe_speed_ = val;
+  }
+  static int min_scaling_span_in_pixels() {
+    return min_scaling_span_in_pixels_;
+  };
+  static void set_min_scaling_span_in_pixels(int val) {
+    min_scaling_span_in_pixels_ = val;
+  }
+  static int show_press_delay_in_ms() {
+    return show_press_delay_in_ms_;
+  }
+  static int set_show_press_delay_in_ms(int val) {
+    return show_press_delay_in_ms_ = val;
+  }
+  static int scroll_debounce_interval_in_ms() {
+    return scroll_debounce_interval_in_ms_;
+  }
+  static int set_scroll_debounce_interval_in_ms(int val) {
+    return scroll_debounce_interval_in_ms_ = val;
+  }
+  static float fling_velocity_cap() {
+    return fling_velocity_cap_;
+  }
+  static void set_fling_velocity_cap(float val) {
+    fling_velocity_cap_ = val;
+  }
+  // TODO(davemoore): Move into chrome/browser/ui.
+  static int tab_scrub_activation_delay_in_ms() {
+    return tab_scrub_activation_delay_in_ms_;
+  }
+  static void set_tab_scrub_activation_delay_in_ms(int val) {
+    tab_scrub_activation_delay_in_ms_ = val;
+  }
+
+ private:
+  // These are listed in alphabetical order ignoring underscores, to
+  // align with the associated list of preferences in
+  // gesture_prefs_aura.cc. These two lists should be kept in sync.
+
+  // The default touch radius length used when the only information given
+  // by the device is the touch center.
+  static int default_radius_;
+
+  // The maximum allowed distance between two fingers for a two finger tap. If
+  // the distance between two fingers is greater than this value, we will not
+  // recognize a two finger tap.
+  static double max_distance_for_two_finger_tap_in_pixels_;
+
+  // Maximum time between a GestureFlingCancel and a mousedown such that the
+  // mousedown is considered associated with the cancel event.
+  static int fling_max_cancel_to_down_time_in_ms_;
+
+  // Maxium time between a mousedown/mouseup pair that is considered to be a
+  // suppressable tap.
+  static int fling_max_tap_gap_time_in_ms_;
+
+  static double long_press_time_in_seconds_;
+  static double semi_long_press_time_in_seconds_;
+  static double max_seconds_between_double_click_;
+  static double max_separation_for_gesture_touches_in_pixels_;
+  static float max_swipe_deviation_angle_;
+  static double max_touch_down_duration_in_seconds_for_click_;
+  static double max_touch_move_in_pixels_for_click_;
+  static double max_distance_between_taps_for_double_tap_;
+  static double min_distance_for_pinch_scroll_in_pixels_;
+  // Only used with --compensate-for-unstable-pinch-zoom.
+  static double min_pinch_update_distance_in_pixels_;
+  static float min_scroll_velocity_;
+  static double min_swipe_speed_;
+  static int min_scaling_span_in_pixels_;
+  static int show_press_delay_in_ms_;
+  static int scroll_debounce_interval_in_ms_;
+  static float fling_velocity_cap_;
+  // TODO(davemoore): Move into chrome/browser/ui.
+  static int tab_scrub_activation_delay_in_ms_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureConfiguration);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_
diff --git a/ui/events/gestures/gesture_provider_aura.cc b/ui/events/gestures/gesture_provider_aura.cc
new file mode 100644
index 0000000..a731357
--- /dev/null
+++ b/ui/events/gestures/gesture_provider_aura.cc
@@ -0,0 +1,138 @@
+// Copyright 2014 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 "ui/events/gestures/gesture_provider_aura.h"
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "ui/events/event.h"
+#include "ui/events/gesture_detection/gesture_config_helper.h"
+#include "ui/events/gesture_detection/gesture_event_data.h"
+#include "ui/events/gestures/gesture_configuration.h"
+
+namespace ui {
+
+GestureProviderAura::GestureProviderAura(GestureProviderAuraClient* client)
+    : client_(client),
+      filtered_gesture_provider_(ui::DefaultGestureProviderConfig(), this),
+      handling_event_(false) {
+  filtered_gesture_provider_.SetDoubleTapSupportForPlatformEnabled(false);
+}
+
+GestureProviderAura::~GestureProviderAura() {}
+
+bool GestureProviderAura::OnTouchEvent(const TouchEvent& event) {
+  int index = pointer_state_.FindPointerIndexOfId(event.touch_id());
+  bool pointer_id_is_active = index != -1;
+
+  if (event.type() == ET_TOUCH_PRESSED && pointer_id_is_active) {
+    // Ignore touch press events if we already believe the pointer is down.
+    return false;
+  } else if (event.type() != ET_TOUCH_PRESSED && !pointer_id_is_active) {
+    // We could have an active touch stream transfered to us, resulting in touch
+    // move or touch up events without associated touch down events. Ignore
+    // them.
+    return false;
+  }
+
+  // If this is a touchmove event, and it isn't different from the last
+  // event, ignore it.
+  if (event.type() == ET_TOUCH_MOVED &&
+      event.x() == pointer_state_.GetX(index) &&
+      event.y() == pointer_state_.GetY(index)) {
+    return false;
+  }
+
+  last_touch_event_latency_info_ = *event.latency();
+  pointer_state_.OnTouch(event);
+
+  bool result = filtered_gesture_provider_.OnTouchEvent(pointer_state_);
+  pointer_state_.CleanupRemovedTouchPoints(event);
+  return result;
+}
+
+void GestureProviderAura::OnTouchEventAck(bool event_consumed) {
+  DCHECK(pending_gestures_.empty());
+  DCHECK(!handling_event_);
+  base::AutoReset<bool> handling_event(&handling_event_, true);
+  filtered_gesture_provider_.OnTouchEventAck(event_consumed);
+  last_touch_event_latency_info_.Clear();
+}
+
+void GestureProviderAura::OnGestureEvent(
+    const GestureEventData& gesture) {
+  GestureEventDetails details = gesture.details;
+  details.set_oldest_touch_id(gesture.motion_event_id);
+
+  if (gesture.type() == ET_GESTURE_TAP) {
+    int tap_count = 1;
+    if (previous_tap_ && IsConsideredDoubleTap(*previous_tap_, gesture))
+      tap_count = 1 + (previous_tap_->details.tap_count() % 3);
+    details.set_tap_count(tap_count);
+    if (!previous_tap_)
+      previous_tap_.reset(new GestureEventData(gesture));
+    else
+      *previous_tap_ = gesture;
+    previous_tap_->details = details;
+  } else if (gesture.type() == ET_GESTURE_TAP_CANCEL) {
+    previous_tap_.reset();
+  }
+
+  scoped_ptr<ui::GestureEvent> event(
+      new ui::GestureEvent(gesture.x,
+                           gesture.y,
+                           gesture.flags,
+                           gesture.time - base::TimeTicks(),
+                           details));
+
+  ui::LatencyInfo* gesture_latency = event->latency();
+
+  gesture_latency->CopyLatencyFrom(
+      last_touch_event_latency_info_,
+      ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
+  gesture_latency->CopyLatencyFrom(
+      last_touch_event_latency_info_,
+      ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
+  gesture_latency->CopyLatencyFrom(
+      last_touch_event_latency_info_,
+      ui::INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT);
+
+  if (!handling_event_) {
+    // Dispatching event caused by timer.
+    client_->OnGestureEvent(event.get());
+  } else {
+    // Memory managed by ScopedVector pending_gestures_.
+    pending_gestures_.push_back(event.release());
+  }
+}
+
+ScopedVector<GestureEvent>* GestureProviderAura::GetAndResetPendingGestures() {
+  if (pending_gestures_.empty())
+    return NULL;
+  // Caller is responsible for deleting old_pending_gestures.
+  ScopedVector<GestureEvent>* old_pending_gestures =
+      new ScopedVector<GestureEvent>();
+  old_pending_gestures->swap(pending_gestures_);
+  return old_pending_gestures;
+}
+
+bool GestureProviderAura::IsConsideredDoubleTap(
+    const GestureEventData& previous_tap,
+    const GestureEventData& current_tap) const {
+  if (current_tap.time - previous_tap.time >
+      base::TimeDelta::FromMilliseconds(
+          ui::GestureConfiguration::max_seconds_between_double_click() *
+          1000)) {
+    return false;
+  }
+
+  double double_tap_slop_square =
+      GestureConfiguration::max_distance_between_taps_for_double_tap();
+  double_tap_slop_square *= double_tap_slop_square;
+  const float delta_x = previous_tap.x - current_tap.x;
+  const float delta_y = previous_tap.y - current_tap.y;
+  return (delta_x * delta_x + delta_y * delta_y < double_tap_slop_square);
+}
+
+}  // namespace content
diff --git a/ui/events/gestures/gesture_provider_aura.h b/ui/events/gestures/gesture_provider_aura.h
new file mode 100644
index 0000000..712b84f
--- /dev/null
+++ b/ui/events/gestures/gesture_provider_aura.h
@@ -0,0 +1,59 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_UI_GESTURE_PROVIDER_H_
+#define UI_EVENTS_GESTURE_DETECTION_UI_GESTURE_PROVIDER_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_vector.h"
+#include "ui/events/event.h"
+#include "ui/events/events_export.h"
+#include "ui/events/gesture_detection/filtered_gesture_provider.h"
+#include "ui/events/gesture_detection/gesture_event_data_packet.h"
+#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h"
+#include "ui/events/gestures/motion_event_aura.h"
+
+namespace ui {
+
+class EVENTS_EXPORT GestureProviderAuraClient {
+ public:
+  virtual ~GestureProviderAuraClient() {}
+  virtual void OnGestureEvent(GestureEvent* event) = 0;
+};
+
+// Provides gesture detection and dispatch given a sequence of touch events
+// and touch event acks.
+class EVENTS_EXPORT GestureProviderAura : public GestureProviderClient {
+ public:
+  GestureProviderAura(GestureProviderAuraClient* client);
+  virtual ~GestureProviderAura();
+
+  bool OnTouchEvent(const TouchEvent& event);
+  void OnTouchEventAck(bool event_consumed);
+  const MotionEventAura& pointer_state() { return pointer_state_; }
+  ScopedVector<GestureEvent>* GetAndResetPendingGestures();
+
+  // GestureProviderClient implementation
+  virtual void OnGestureEvent(const GestureEventData& gesture) OVERRIDE;
+
+ private:
+  bool IsConsideredDoubleTap(const GestureEventData& previous_tap,
+                             const GestureEventData& current_tap) const;
+
+  scoped_ptr<GestureEventData> previous_tap_;
+
+  GestureProviderAuraClient* client_;
+  MotionEventAura pointer_state_;
+  FilteredGestureProvider filtered_gesture_provider_;
+
+  ui::LatencyInfo last_touch_event_latency_info_;
+  bool handling_event_;
+  ScopedVector<GestureEvent> pending_gestures_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureProviderAura);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_UI_GESTURE_PROVIDER_H_
diff --git a/ui/events/gestures/gesture_provider_aura_unittest.cc b/ui/events/gestures/gesture_provider_aura_unittest.cc
new file mode 100644
index 0000000..e2b1523
--- /dev/null
+++ b/ui/events/gestures/gesture_provider_aura_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright 2014 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 "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/gestures/gesture_provider_aura.h"
+
+namespace ui {
+
+class GestureProviderAuraTest : public testing::Test,
+                                public GestureProviderAuraClient {
+ public:
+  GestureProviderAuraTest() {}
+
+  virtual ~GestureProviderAuraTest() {}
+
+  virtual void OnGestureEvent(GestureEvent* event) OVERRIDE {}
+
+  virtual void SetUp() OVERRIDE {
+    provider_.reset(new GestureProviderAura(this));
+  }
+
+  virtual void TearDown() OVERRIDE { provider_.reset(); }
+
+  GestureProviderAura* provider() { return provider_.get(); }
+
+ private:
+  scoped_ptr<GestureProviderAura> provider_;
+  base::MessageLoopForUI message_loop_;
+};
+
+TEST_F(GestureProviderAuraTest, IgnoresExtraPressEvents) {
+  base::TimeDelta time = ui::EventTimeForNow();
+  TouchEvent press1(ET_TOUCH_PRESSED, gfx::PointF(10, 10), 0, time);
+  EXPECT_TRUE(provider()->OnTouchEvent(press1));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent press2(ET_TOUCH_PRESSED, gfx::PointF(30, 40), 0, time);
+  // Redundant press with same id is ignored.
+  EXPECT_FALSE(provider()->OnTouchEvent(press2));
+}
+
+TEST_F(GestureProviderAuraTest, IgnoresExtraMoveOrReleaseEvents) {
+  base::TimeDelta time = ui::EventTimeForNow();
+  TouchEvent press1(ET_TOUCH_PRESSED, gfx::PointF(10, 10), 0, time);
+  EXPECT_TRUE(provider()->OnTouchEvent(press1));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent release1(ET_TOUCH_RELEASED, gfx::PointF(30, 40), 0, time);
+  EXPECT_TRUE(provider()->OnTouchEvent(release1));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent release2(ET_TOUCH_RELEASED, gfx::PointF(30, 45), 0, time);
+  EXPECT_FALSE(provider()->OnTouchEvent(release1));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent move1(ET_TOUCH_MOVED, gfx::PointF(70, 75), 0, time);
+  EXPECT_FALSE(provider()->OnTouchEvent(move1));
+}
+
+TEST_F(GestureProviderAuraTest, IgnoresIdenticalMoveEvents) {
+  const float kRadiusX = 20.f;
+  const float kRadiusY = 30.f;
+  const float kAngle = 0.321f;
+  const float kForce = 40.f;
+  const int kTouchId0 = 5;
+  const int kTouchId1 = 3;
+
+  base::TimeDelta time = ui::EventTimeForNow();
+  TouchEvent press0_1(ET_TOUCH_PRESSED, gfx::PointF(9, 10), kTouchId0, time);
+  EXPECT_TRUE(provider()->OnTouchEvent(press0_1));
+
+  TouchEvent press1_1(ET_TOUCH_PRESSED, gfx::PointF(40, 40), kTouchId1, time);
+  EXPECT_TRUE(provider()->OnTouchEvent(press1_1));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent move0_1(ET_TOUCH_MOVED,
+                   gfx::PointF(10, 10),
+                   0,
+                   kTouchId0,
+                   time,
+                   kRadiusX,
+                   kRadiusY,
+                   kAngle,
+                   kForce);
+  EXPECT_TRUE(provider()->OnTouchEvent(move0_1));
+
+  TouchEvent move1_1(ET_TOUCH_MOVED,
+                   gfx::PointF(100, 200),
+                   0,
+                   kTouchId1,
+                   time,
+                   kRadiusX,
+                   kRadiusY,
+                   kAngle,
+                   kForce);
+  EXPECT_TRUE(provider()->OnTouchEvent(move1_1));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent move0_2(ET_TOUCH_MOVED,
+                     gfx::PointF(10, 10),
+                     0,
+                     kTouchId0,
+                     time,
+                     kRadiusX,
+                     kRadiusY,
+                     kAngle,
+                     kForce);
+  // Nothing has changed, so ignore the move.
+  EXPECT_FALSE(provider()->OnTouchEvent(move0_2));
+
+  TouchEvent move1_2(ET_TOUCH_MOVED,
+                     gfx::PointF(100, 200),
+                     0,
+                     kTouchId1,
+                     time,
+                     kRadiusX,
+                     kRadiusY,
+                     kAngle,
+                     kForce);
+  // Nothing has changed, so ignore the move.
+  EXPECT_FALSE(provider()->OnTouchEvent(move1_2));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent move0_3(ET_TOUCH_MOVED,
+                     gfx::PointF(70, 75.1f),
+                     0,
+                     kTouchId0,
+                     time,
+                     kRadiusX,
+                     kRadiusY,
+                     kAngle,
+                     kForce);
+  // Position has changed, so don't ignore the move.
+  EXPECT_TRUE(provider()->OnTouchEvent(move0_3));
+
+  time += base::TimeDelta::FromMilliseconds(10);
+  TouchEvent move0_4(ET_TOUCH_MOVED,
+                     gfx::PointF(70, 75.1f),
+                     0,
+                     kTouchId0,
+                     time,
+                     kRadiusX,
+                     kRadiusY + 1,
+                     kAngle,
+                     kForce);
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer.h b/ui/events/gestures/gesture_recognizer.h
new file mode 100644
index 0000000..5dc5834
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2012 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 UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_H_
+#define UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_H_
+
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_export.h"
+#include "ui/events/gestures/gesture_types.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace ui {
+// A GestureRecognizer is an abstract base class for conversion of touch events
+// into gestures.
+class EVENTS_EXPORT GestureRecognizer {
+ public:
+  static GestureRecognizer* Create();
+  static GestureRecognizer* Get();
+  static void Reset();
+
+  // List of GestureEvent*.
+  typedef ScopedVector<GestureEvent> Gestures;
+
+  virtual ~GestureRecognizer() {}
+
+  // Invoked before event dispatch. If the event is invalid given the current
+  // touch sequence, marks it as handled.
+  virtual bool ProcessTouchEventPreDispatch(const TouchEvent& event,
+                                            GestureConsumer* consumer) = 0;
+  // Returns a list of zero or more GestureEvents. The caller is responsible for
+  // freeing the returned events. Called synchronously after event dispatch.
+  virtual Gestures* ProcessTouchEventPostDispatch(
+      const TouchEvent& event,
+      ui::EventResult result,
+      GestureConsumer* consumer) = 0;
+  // Returns a list of zero or more GestureEvents. The caller is responsible for
+  // freeing the returned events. Called when a touch event receives an
+  // asynchronous ack.
+  virtual Gestures* ProcessTouchEventOnAsyncAck(const TouchEvent& event,
+                                                ui::EventResult result,
+                                                GestureConsumer* consumer) = 0;
+
+  // This is called when the consumer is destroyed. So this should cleanup any
+  // internal state maintained for |consumer|. Returns true iff there was
+  // state relating to |consumer| to clean up.
+  virtual bool CleanupStateForConsumer(GestureConsumer* consumer) = 0;
+
+  // Return the window which should handle this TouchEvent, in the case where
+  // the touch is already associated with a target.
+  // Otherwise, returns null.
+  virtual GestureConsumer* GetTouchLockedTarget(const TouchEvent& event) = 0;
+
+  // Return the window which should handle this GestureEvent.
+  virtual GestureConsumer* GetTargetForGestureEvent(
+      const GestureEvent& event) = 0;
+
+  // Returns the target of the nearest active touch with source device of
+  // |source_device_id|, within
+  // GestureConfiguration::max_separation_for_gesture_touches_in_pixels of
+  // |location|, or NULL if no such point exists.
+  virtual GestureConsumer* GetTargetForLocation(
+      const gfx::PointF& location, int source_device_id) = 0;
+
+  // Makes |new_consumer| the target for events previously targeting
+  // |current_consumer|. All other targets are canceled.
+  // The caller is responsible for updating the state of the consumers to
+  // be aware of this transfer of control (there are no ENTERED/EXITED events).
+  // If |new_consumer| is NULL, all events are canceled.
+  // If |old_consumer| is NULL, all events not already targeting |new_consumer|
+  // are canceled.
+  virtual void TransferEventsTo(GestureConsumer* current_consumer,
+                                GestureConsumer* new_consumer) = 0;
+
+  // If a gesture is underway for |consumer| |point| is set to the last touch
+  // point and true is returned. If no touch events have been processed for
+  // |consumer| false is returned and |point| is untouched.
+  virtual bool GetLastTouchPointForTarget(GestureConsumer* consumer,
+                                          gfx::PointF* point) = 0;
+
+  // Sends a touch cancel event for every active touch. Returns true iff any
+  // touch cancels were sent.
+  virtual bool CancelActiveTouches(GestureConsumer* consumer) = 0;
+
+  // Subscribes |helper| for dispatching async gestures such as long press.
+  // The Gesture Recognizer does NOT take ownership of |helper| and it is the
+  // responsibility of the |helper| to call |RemoveGestureEventHelper()| on
+  // destruction.
+  virtual void AddGestureEventHelper(GestureEventHelper* helper) = 0;
+
+  // Unsubscribes |helper| from async gesture dispatch.
+  // Since the GestureRecognizer does not own the |helper|, it is not deleted
+  // and must be cleaned up appropriately by the caller.
+  virtual void RemoveGestureEventHelper(GestureEventHelper* helper) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_H_
diff --git a/ui/events/gestures/gesture_recognizer_impl.cc b/ui/events/gestures/gesture_recognizer_impl.cc
new file mode 100644
index 0000000..9737fd8
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl.cc
@@ -0,0 +1,348 @@
+// Copyright (c) 2012 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 "ui/events/gestures/gesture_recognizer_impl.h"
+
+#include <limits>
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_switches.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/gestures/gesture_configuration.h"
+#include "ui/events/gestures/gesture_types.h"
+
+namespace ui {
+
+namespace {
+
+template <typename T>
+void TransferConsumer(GestureConsumer* current_consumer,
+                      GestureConsumer* new_consumer,
+                      std::map<GestureConsumer*, T>* map) {
+  if (map->count(current_consumer)) {
+    (*map)[new_consumer] = (*map)[current_consumer];
+    map->erase(current_consumer);
+  }
+}
+
+bool RemoveConsumerFromMap(GestureConsumer* consumer,
+                           GestureRecognizerImpl::TouchIdToConsumerMap* map) {
+  bool consumer_removed = false;
+  for (GestureRecognizerImpl::TouchIdToConsumerMap::iterator i = map->begin();
+       i != map->end();) {
+    if (i->second == consumer) {
+      map->erase(i++);
+      consumer_removed = true;
+    } else {
+      ++i;
+    }
+  }
+  return consumer_removed;
+}
+
+void TransferTouchIdToConsumerMap(
+    GestureConsumer* old_consumer,
+    GestureConsumer* new_consumer,
+    GestureRecognizerImpl::TouchIdToConsumerMap* map) {
+  for (GestureRecognizerImpl::TouchIdToConsumerMap::iterator i = map->begin();
+       i != map->end(); ++i) {
+    if (i->second == old_consumer)
+      i->second = new_consumer;
+  }
+}
+
+GestureProviderAura* CreateGestureProvider(GestureProviderAuraClient* client) {
+  return new GestureProviderAura(client);
+}
+
+}  // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerImpl, public:
+
+GestureRecognizerImpl::GestureRecognizerImpl() {
+}
+
+GestureRecognizerImpl::~GestureRecognizerImpl() {
+  STLDeleteValues(&consumer_gesture_provider_);
+}
+
+// Checks if this finger is already down, if so, returns the current target.
+// Otherwise, returns NULL.
+GestureConsumer* GestureRecognizerImpl::GetTouchLockedTarget(
+    const TouchEvent& event) {
+  return touch_id_target_[event.touch_id()];
+}
+
+GestureConsumer* GestureRecognizerImpl::GetTargetForGestureEvent(
+    const GestureEvent& event) {
+  GestureConsumer* target = NULL;
+  int touch_id = event.details().oldest_touch_id();
+  target = touch_id_target_for_gestures_[touch_id];
+  return target;
+}
+
+GestureConsumer* GestureRecognizerImpl::GetTargetForLocation(
+    const gfx::PointF& location, int source_device_id) {
+  const int max_distance =
+      GestureConfiguration::max_separation_for_gesture_touches_in_pixels();
+
+  gfx::PointF closest_point;
+  int closest_touch_id;
+  float closest_distance_squared = std::numeric_limits<float>::infinity();
+
+  std::map<GestureConsumer*, GestureProviderAura*>::iterator i;
+  for (i = consumer_gesture_provider_.begin();
+       i != consumer_gesture_provider_.end();
+       ++i) {
+    const MotionEventAura& pointer_state = i->second->pointer_state();
+    for (size_t j = 0; j < pointer_state.GetPointerCount(); ++j) {
+      if (source_device_id != pointer_state.GetSourceDeviceId(j))
+        continue;
+      gfx::PointF point(pointer_state.GetX(j), pointer_state.GetY(j));
+      // Relative distance is all we need here, so LengthSquared() is
+      // appropriate, and cheaper than Length().
+      float distance_squared = (point - location).LengthSquared();
+      if (distance_squared < closest_distance_squared) {
+        closest_point = point;
+        closest_touch_id = pointer_state.GetPointerId(j);
+        closest_distance_squared = distance_squared;
+      }
+    }
+  }
+
+  if (closest_distance_squared < max_distance * max_distance)
+    return touch_id_target_[closest_touch_id];
+  return NULL;
+}
+
+void GestureRecognizerImpl::TransferEventsTo(GestureConsumer* current_consumer,
+                                             GestureConsumer* new_consumer) {
+  // Send cancel to all those save |new_consumer| and |current_consumer|.
+  // Don't send a cancel to |current_consumer|, unless |new_consumer| is NULL.
+  // Dispatching a touch-cancel event can end up altering |touch_id_target_|
+  // (e.g. when the target of the event is destroyed, causing it to be removed
+  // from |touch_id_target_| in |CleanupStateForConsumer()|). So create a list
+  // of the touch-ids that need to be cancelled, and dispatch the cancel events
+  // for them at the end.
+
+  std::vector<GestureConsumer*> consumers;
+  std::map<GestureConsumer*, GestureProviderAura*>::iterator i;
+  for (i = consumer_gesture_provider_.begin();
+       i != consumer_gesture_provider_.end();
+       ++i) {
+    if (i->first && i->first != new_consumer &&
+        (i->first != current_consumer || new_consumer == NULL)) {
+      consumers.push_back(i->first);
+    }
+  }
+  for (std::vector<GestureConsumer*>::iterator iter = consumers.begin();
+       iter != consumers.end();
+       ++iter) {
+    CancelActiveTouches(*iter);
+  }
+  // Transfer events from |current_consumer| to |new_consumer|.
+  if (current_consumer && new_consumer) {
+    TransferTouchIdToConsumerMap(current_consumer, new_consumer,
+                                 &touch_id_target_);
+    TransferTouchIdToConsumerMap(current_consumer, new_consumer,
+                                 &touch_id_target_for_gestures_);
+    TransferConsumer(
+        current_consumer, new_consumer, &consumer_gesture_provider_);
+  }
+}
+
+bool GestureRecognizerImpl::GetLastTouchPointForTarget(
+    GestureConsumer* consumer,
+    gfx::PointF* point) {
+    if (consumer_gesture_provider_.count(consumer) == 0)
+      return false;
+    const MotionEvent& pointer_state =
+        consumer_gesture_provider_[consumer]->pointer_state();
+    *point = gfx::PointF(pointer_state.GetX(), pointer_state.GetY());
+    return true;
+}
+
+bool GestureRecognizerImpl::CancelActiveTouches(GestureConsumer* consumer) {
+  bool cancelled_touch = false;
+  if (consumer_gesture_provider_.count(consumer) == 0)
+    return false;
+  const MotionEventAura& pointer_state =
+      consumer_gesture_provider_[consumer]->pointer_state();
+  if (pointer_state.GetPointerCount() == 0)
+    return false;
+  // Pointer_state is modified every time after DispatchCancelTouchEvent.
+  scoped_ptr<MotionEvent> pointer_state_clone = pointer_state.Clone();
+  for (size_t i = 0; i < pointer_state_clone->GetPointerCount(); ++i) {
+    gfx::PointF point(pointer_state_clone->GetX(i),
+                      pointer_state_clone->GetY(i));
+    TouchEvent touch_event(ui::ET_TOUCH_CANCELLED,
+                           point,
+                           ui::EF_IS_SYNTHESIZED,
+                           pointer_state_clone->GetPointerId(i),
+                           ui::EventTimeForNow(),
+                           0.0f,
+                           0.0f,
+                           0.0f,
+                           0.0f);
+    GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
+    if (helper)
+      helper->DispatchCancelTouchEvent(&touch_event);
+    cancelled_touch = true;
+  }
+  return cancelled_touch;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GestureRecognizerImpl, private:
+
+GestureProviderAura* GestureRecognizerImpl::GetGestureProviderForConsumer(
+    GestureConsumer* consumer) {
+  GestureProviderAura* gesture_provider = consumer_gesture_provider_[consumer];
+  if (!gesture_provider) {
+    gesture_provider = CreateGestureProvider(this);
+    consumer_gesture_provider_[consumer] = gesture_provider;
+  }
+  return gesture_provider;
+}
+
+void GestureRecognizerImpl::SetupTargets(const TouchEvent& event,
+                                         GestureConsumer* target) {
+  if (event.type() == ui::ET_TOUCH_RELEASED ||
+      event.type() == ui::ET_TOUCH_CANCELLED) {
+    touch_id_target_.erase(event.touch_id());
+  } else if (event.type() == ui::ET_TOUCH_PRESSED) {
+    touch_id_target_[event.touch_id()] = target;
+    if (target)
+      touch_id_target_for_gestures_[event.touch_id()] = target;
+  }
+}
+
+void GestureRecognizerImpl::DispatchGestureEvent(GestureEvent* event) {
+  GestureConsumer* consumer = GetTargetForGestureEvent(*event);
+  if (consumer) {
+    GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
+    if (helper)
+      helper->DispatchGestureEvent(event);
+  }
+}
+
+bool GestureRecognizerImpl::ProcessTouchEventPreDispatch(
+    const TouchEvent& event,
+    GestureConsumer* consumer) {
+  SetupTargets(event, consumer);
+
+  if (event.result() & ER_CONSUMED)
+    return false;
+
+  GestureProviderAura* gesture_provider =
+      GetGestureProviderForConsumer(consumer);
+  return gesture_provider->OnTouchEvent(event);
+}
+
+GestureRecognizer::Gestures*
+GestureRecognizerImpl::ProcessTouchEventPostDispatch(
+    const TouchEvent& event,
+    ui::EventResult result,
+    GestureConsumer* consumer) {
+  GestureProviderAura* gesture_provider =
+      GetGestureProviderForConsumer(consumer);
+  gesture_provider->OnTouchEventAck(result != ER_UNHANDLED);
+  return gesture_provider->GetAndResetPendingGestures();
+}
+
+GestureRecognizer::Gestures* GestureRecognizerImpl::ProcessTouchEventOnAsyncAck(
+    const TouchEvent& event,
+    ui::EventResult result,
+    GestureConsumer* consumer) {
+  if (result & ui::ER_CONSUMED)
+    return NULL;
+  GestureProviderAura* gesture_provider =
+      GetGestureProviderForConsumer(consumer);
+  gesture_provider->OnTouchEventAck(result != ER_UNHANDLED);
+  return gesture_provider->GetAndResetPendingGestures();
+}
+
+bool GestureRecognizerImpl::CleanupStateForConsumer(
+    GestureConsumer* consumer) {
+  bool state_cleaned_up = false;
+
+  if (consumer_gesture_provider_.count(consumer)) {
+    state_cleaned_up = true;
+    delete consumer_gesture_provider_[consumer];
+    consumer_gesture_provider_.erase(consumer);
+  }
+
+  state_cleaned_up |= RemoveConsumerFromMap(consumer, &touch_id_target_);
+  state_cleaned_up |=
+      RemoveConsumerFromMap(consumer, &touch_id_target_for_gestures_);
+  return state_cleaned_up;
+}
+
+void GestureRecognizerImpl::AddGestureEventHelper(GestureEventHelper* helper) {
+  helpers_.push_back(helper);
+}
+
+void GestureRecognizerImpl::RemoveGestureEventHelper(
+    GestureEventHelper* helper) {
+  std::vector<GestureEventHelper*>::iterator it = std::find(helpers_.begin(),
+      helpers_.end(), helper);
+  if (it != helpers_.end())
+    helpers_.erase(it);
+}
+
+void GestureRecognizerImpl::OnGestureEvent(GestureEvent* event) {
+  DispatchGestureEvent(event);
+}
+
+GestureEventHelper* GestureRecognizerImpl::FindDispatchHelperForConsumer(
+    GestureConsumer* consumer) {
+  std::vector<GestureEventHelper*>::iterator it;
+  for (it = helpers_.begin(); it != helpers_.end(); ++it) {
+    if ((*it)->CanDispatchToConsumer(consumer))
+      return (*it);
+  }
+  return NULL;
+}
+
+// GestureRecognizer, static
+GestureRecognizer* GestureRecognizer::Create() {
+  return new GestureRecognizerImpl();
+}
+
+static GestureRecognizerImpl* g_gesture_recognizer_instance = NULL;
+
+// GestureRecognizer, static
+GestureRecognizer* GestureRecognizer::Get() {
+  if (!g_gesture_recognizer_instance)
+    g_gesture_recognizer_instance = new GestureRecognizerImpl();
+  return g_gesture_recognizer_instance;
+}
+
+// GestureRecognizer, static
+void GestureRecognizer::Reset() {
+  delete g_gesture_recognizer_instance;
+  g_gesture_recognizer_instance = NULL;
+}
+
+void SetGestureRecognizerForTesting(GestureRecognizer* gesture_recognizer) {
+  // Transfer helpers to the new GR.
+  std::vector<GestureEventHelper*>& helpers =
+      g_gesture_recognizer_instance->helpers();
+  std::vector<GestureEventHelper*>::iterator it;
+  for (it = helpers.begin(); it != helpers.end(); ++it)
+    gesture_recognizer->AddGestureEventHelper(*it);
+
+  helpers.clear();
+  g_gesture_recognizer_instance =
+      static_cast<GestureRecognizerImpl*>(gesture_recognizer);
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_recognizer_impl.h b/ui/events/gestures/gesture_recognizer_impl.h
new file mode 100644
index 0000000..7ba74f3
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl.h
@@ -0,0 +1,106 @@
+// Copyright (c) 2012 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 UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
+#define UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
+
+#include <map>
+#include <vector>
+
+#include "base/memory/linked_ptr.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_export.h"
+#include "ui/events/gestures/gesture_provider_aura.h"
+#include "ui/events/gestures/gesture_recognizer.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+class GestureConsumer;
+class GestureEvent;
+class GestureEventHelper;
+class TouchEvent;
+
+// TODO(tdresser): Once the unified gesture recognition process sticks
+// (crbug.com/332418), GestureRecognizerImpl can be cleaned up
+// significantly.
+class EVENTS_EXPORT GestureRecognizerImpl : public GestureRecognizer,
+                                            public GestureProviderAuraClient {
+ public:
+  typedef std::map<int, GestureConsumer*> TouchIdToConsumerMap;
+
+  GestureRecognizerImpl();
+  virtual ~GestureRecognizerImpl();
+
+  std::vector<GestureEventHelper*>& helpers() { return helpers_; }
+
+  // Overridden from GestureRecognizer
+  virtual GestureConsumer* GetTouchLockedTarget(
+      const TouchEvent& event) OVERRIDE;
+  virtual GestureConsumer* GetTargetForGestureEvent(
+      const GestureEvent& event) OVERRIDE;
+  virtual GestureConsumer* GetTargetForLocation(
+      const gfx::PointF& location, int source_device_id) OVERRIDE;
+  virtual void TransferEventsTo(GestureConsumer* current_consumer,
+                                GestureConsumer* new_consumer) OVERRIDE;
+  virtual bool GetLastTouchPointForTarget(GestureConsumer* consumer,
+                                          gfx::PointF* point) OVERRIDE;
+  virtual bool CancelActiveTouches(GestureConsumer* consumer) OVERRIDE;
+
+ protected:
+  virtual GestureProviderAura* GetGestureProviderForConsumer(
+      GestureConsumer* c);
+
+ private:
+  // Sets up the target consumer for gestures based on the touch-event.
+  void SetupTargets(const TouchEvent& event, GestureConsumer* consumer);
+
+  void DispatchGestureEvent(GestureEvent* event);
+
+  // Overridden from GestureRecognizer
+  virtual bool ProcessTouchEventPreDispatch(const TouchEvent& event,
+                                            GestureConsumer* consumer) OVERRIDE;
+
+  virtual Gestures* ProcessTouchEventPostDispatch(
+      const TouchEvent& event,
+      ui::EventResult result,
+      GestureConsumer* consumer) OVERRIDE;
+
+  virtual Gestures* ProcessTouchEventOnAsyncAck(
+      const TouchEvent& event,
+      ui::EventResult result,
+      GestureConsumer* consumer) OVERRIDE;
+
+  virtual bool CleanupStateForConsumer(GestureConsumer* consumer)
+      OVERRIDE;
+  virtual void AddGestureEventHelper(GestureEventHelper* helper) OVERRIDE;
+  virtual void RemoveGestureEventHelper(GestureEventHelper* helper) OVERRIDE;
+
+  // Overridden from GestureProviderAuraClient
+  virtual void OnGestureEvent(GestureEvent* event) OVERRIDE;
+
+  // Convenience method to find the GestureEventHelper that can dispatch events
+  // to a specific |consumer|.
+  GestureEventHelper* FindDispatchHelperForConsumer(GestureConsumer* consumer);
+  std::map<GestureConsumer*, GestureProviderAura*> consumer_gesture_provider_;
+
+  // Both |touch_id_target_| and |touch_id_target_for_gestures_| map a touch-id
+  // to its target window.  touch-ids are removed from |touch_id_target_| on
+  // ET_TOUCH_RELEASE and ET_TOUCH_CANCEL. |touch_id_target_for_gestures_| are
+  // removed in ConsumerDestroyed().
+  TouchIdToConsumerMap touch_id_target_;
+  TouchIdToConsumerMap touch_id_target_for_gestures_;
+
+  std::vector<GestureEventHelper*> helpers_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImpl);
+};
+
+// Provided only for testing:
+EVENTS_EXPORT void SetGestureRecognizerForTesting(
+    GestureRecognizer* gesture_recognizer);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURES_GESTURE_RECOGNIZER_IMPL_H_
diff --git a/ui/events/gestures/gesture_recognizer_impl_mac.cc b/ui/events/gestures/gesture_recognizer_impl_mac.cc
new file mode 100644
index 0000000..4dfa311
--- /dev/null
+++ b/ui/events/gestures/gesture_recognizer_impl_mac.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 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 "base/macros.h"
+#include "ui/events/gestures/gesture_recognizer.h"
+
+namespace ui {
+
+namespace {
+
+// Stub implementation of GestureRecognizer for Mac. Currently only serves to
+// provide a no-op implementation of TransferEventsTo().
+class GestureRecognizerImplMac : public GestureRecognizer {
+ public:
+  GestureRecognizerImplMac() {}
+  virtual ~GestureRecognizerImplMac() {}
+
+ private:
+  virtual bool ProcessTouchEventPreDispatch(
+      const TouchEvent& event,
+      GestureConsumer* consumer) OVERRIDE {
+    return false;
+  }
+
+  virtual Gestures* ProcessTouchEventPostDispatch(
+      const TouchEvent& event,
+      ui::EventResult result,
+      GestureConsumer* consumer) OVERRIDE {
+    return NULL;
+  }
+  virtual Gestures* ProcessTouchEventOnAsyncAck(
+      const TouchEvent& event,
+      ui::EventResult result,
+      GestureConsumer* consumer) OVERRIDE {
+    return NULL;
+  };
+  virtual bool CleanupStateForConsumer(GestureConsumer* consumer) OVERRIDE {
+    return false;
+  }
+  virtual GestureConsumer* GetTouchLockedTarget(
+      const TouchEvent& event) OVERRIDE {
+    return NULL;
+  }
+  virtual GestureConsumer* GetTargetForGestureEvent(
+      const GestureEvent& event) OVERRIDE {
+    return NULL;
+  }
+  virtual GestureConsumer* GetTargetForLocation(const gfx::PointF& location,
+                                                int source_device_id) OVERRIDE {
+    return NULL;
+  }
+  virtual void TransferEventsTo(GestureConsumer* current_consumer,
+                                GestureConsumer* new_consumer) OVERRIDE {}
+  virtual bool GetLastTouchPointForTarget(GestureConsumer* consumer,
+                                          gfx::PointF* point) OVERRIDE {
+    return false;
+  }
+  virtual bool CancelActiveTouches(GestureConsumer* consumer) OVERRIDE {
+    return false;
+  }
+  virtual void AddGestureEventHelper(GestureEventHelper* helper) OVERRIDE {}
+  virtual void RemoveGestureEventHelper(GestureEventHelper* helper) OVERRIDE {}
+
+  DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImplMac);
+};
+
+}  // namespace
+
+// static
+GestureRecognizer* GestureRecognizer::Get() {
+  CR_DEFINE_STATIC_LOCAL(GestureRecognizerImplMac, instance, ());
+  return &instance;
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/gesture_types.h b/ui/events/gestures/gesture_types.h
new file mode 100644
index 0000000..c15a2fd
--- /dev/null
+++ b/ui/events/gestures/gesture_types.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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 UI_EVENTS_GESTURES_GESTURE_TYPES_H_
+#define UI_EVENTS_GESTURES_GESTURE_TYPES_H_
+
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class GestureEvent;
+class TouchEvent;
+
+// An abstract type for consumers of gesture-events created by the
+// gesture-recognizer.
+class EVENTS_EXPORT GestureConsumer {
+ public:
+  virtual ~GestureConsumer() {}
+};
+
+// GestureEventHelper creates implementation-specific gesture events and
+// can dispatch them.
+class EVENTS_EXPORT GestureEventHelper {
+ public:
+  virtual ~GestureEventHelper() {
+  }
+
+  // Returns true if this helper can dispatch events to |consumer|.
+  virtual bool CanDispatchToConsumer(GestureConsumer* consumer) = 0;
+  virtual void DispatchGestureEvent(GestureEvent* event) = 0;
+  virtual void DispatchCancelTouchEvent(TouchEvent* event) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURES_GESTURE_TYPES_H_
diff --git a/ui/events/gestures/motion_event_aura.cc b/ui/events/gestures/motion_event_aura.cc
new file mode 100644
index 0000000..1b7b825
--- /dev/null
+++ b/ui/events/gestures/motion_event_aura.cc
@@ -0,0 +1,287 @@
+// Copyright 2014 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.
+
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
+#include "ui/events/gestures/motion_event_aura.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "ui/events/gestures/gesture_configuration.h"
+
+namespace ui {
+
+MotionEventAura::MotionEventAura()
+    : pointer_count_(0), cached_action_index_(-1) {
+}
+
+MotionEventAura::MotionEventAura(
+    size_t pointer_count,
+    const base::TimeTicks& last_touch_time,
+    Action cached_action,
+    int cached_action_index,
+    int flags,
+    const PointData (&active_touches)[MotionEvent::MAX_TOUCH_POINT_COUNT])
+    : pointer_count_(pointer_count),
+      last_touch_time_(last_touch_time),
+      cached_action_(cached_action),
+      cached_action_index_(cached_action_index),
+      flags_(flags) {
+  DCHECK(pointer_count_);
+  for (size_t i = 0; i < pointer_count; ++i)
+    active_touches_[i] = active_touches[i];
+}
+
+MotionEventAura::~MotionEventAura() {}
+
+MotionEventAura::PointData MotionEventAura::GetPointDataFromTouchEvent(
+    const TouchEvent& touch) {
+  PointData point_data;
+  point_data.x = touch.x();
+  point_data.y = touch.y();
+  point_data.raw_x = touch.root_location_f().x();
+  point_data.raw_y = touch.root_location_f().y();
+  point_data.touch_id = touch.touch_id();
+  point_data.pressure = touch.force();
+  point_data.source_device_id = touch.source_device_id();
+
+  float radius_x = touch.radius_x();
+  float radius_y = touch.radius_y();
+  float rotation_angle_rad = touch.rotation_angle() * M_PI / 180.f;
+  DCHECK_GE(radius_x, 0) << "Unexpected x-radius < 0";
+  DCHECK_GE(radius_y, 0) << "Unexpected y-radius < 0";
+  DCHECK(0 <= rotation_angle_rad && rotation_angle_rad <= M_PI_2)
+      << "Unexpected touch rotation angle";
+
+  if (radius_x > radius_y) {
+    // The case radius_x == radius_y is omitted from here on purpose: for
+    // circles, we want to pass the angle (which could be any value in such
+    // cases but always seem to be set to zero) unchanged.
+    point_data.touch_major = 2.f * radius_x;
+    point_data.touch_minor = 2.f * radius_y;
+    point_data.orientation = rotation_angle_rad - M_PI_2;
+  } else {
+    point_data.touch_major = 2.f * radius_y;
+    point_data.touch_minor = 2.f * radius_x;
+    point_data.orientation = rotation_angle_rad;
+  }
+
+  if (!point_data.touch_major) {
+    point_data.touch_major = 2.f * GestureConfiguration::default_radius();
+    point_data.touch_minor = 2.f * GestureConfiguration::default_radius();
+    point_data.orientation = 0;
+  }
+
+  return point_data;
+}
+
+void MotionEventAura::OnTouch(const TouchEvent& touch) {
+  switch (touch.type()) {
+    case ET_TOUCH_PRESSED:
+      AddTouch(touch);
+      break;
+    case ET_TOUCH_RELEASED:
+    case ET_TOUCH_CANCELLED:
+      // Removing these touch points needs to be postponed until after the
+      // MotionEvent has been dispatched. This cleanup occurs in
+      // CleanupRemovedTouchPoints.
+      UpdateTouch(touch);
+      break;
+    case ET_TOUCH_MOVED:
+      UpdateTouch(touch);
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+
+  UpdateCachedAction(touch);
+  flags_ = touch.flags();
+  last_touch_time_ = touch.time_stamp() + base::TimeTicks();
+}
+
+int MotionEventAura::GetId() const {
+  return GetPointerId(0);
+}
+
+MotionEvent::Action MotionEventAura::GetAction() const {
+  return cached_action_;
+}
+
+int MotionEventAura::GetActionIndex() const {
+  DCHECK(cached_action_ == ACTION_POINTER_DOWN ||
+         cached_action_ == ACTION_POINTER_UP);
+  DCHECK_GE(cached_action_index_, 0);
+  DCHECK_LT(cached_action_index_, static_cast<int>(pointer_count_));
+  return cached_action_index_;
+}
+
+size_t MotionEventAura::GetPointerCount() const { return pointer_count_; }
+
+int MotionEventAura::GetPointerId(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].touch_id;
+}
+
+float MotionEventAura::GetX(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].x;
+}
+
+float MotionEventAura::GetY(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].y;
+}
+
+float MotionEventAura::GetRawX(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].raw_x;
+}
+
+float MotionEventAura::GetRawY(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].raw_y;
+}
+
+float MotionEventAura::GetTouchMajor(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].touch_major;
+}
+
+float MotionEventAura::GetTouchMinor(size_t pointer_index) const {
+  DCHECK_LE(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].touch_minor;
+}
+
+float MotionEventAura::GetOrientation(size_t pointer_index) const {
+  DCHECK_LE(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].orientation;
+}
+
+float MotionEventAura::GetPressure(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].pressure;
+}
+
+MotionEvent::ToolType MotionEventAura::GetToolType(size_t pointer_index) const {
+  // TODO(jdduke): Plumb tool type from the platform, crbug.com/404128.
+  DCHECK_LT(pointer_index, pointer_count_);
+  return MotionEvent::TOOL_TYPE_UNKNOWN;
+}
+
+int MotionEventAura::GetButtonState() const {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+int MotionEventAura::GetFlags() const {
+  return flags_;
+}
+
+base::TimeTicks MotionEventAura::GetEventTime() const {
+  return last_touch_time_;
+}
+
+scoped_ptr<MotionEvent> MotionEventAura::Clone() const {
+  return scoped_ptr<MotionEvent>(new MotionEventAura(pointer_count_,
+                                                     last_touch_time_,
+                                                     cached_action_,
+                                                     cached_action_index_,
+                                                     flags_,
+                                                     active_touches_));
+}
+scoped_ptr<MotionEvent> MotionEventAura::Cancel() const {
+  return scoped_ptr<MotionEvent>(new MotionEventAura(
+      pointer_count_, last_touch_time_, ACTION_CANCEL, -1, 0, active_touches_));
+}
+
+void MotionEventAura::CleanupRemovedTouchPoints(const TouchEvent& event) {
+  if (event.type() != ET_TOUCH_RELEASED &&
+      event.type() != ET_TOUCH_CANCELLED) {
+    return;
+  }
+
+  int index_to_delete = static_cast<int>(GetIndexFromId(event.touch_id()));
+  pointer_count_--;
+  active_touches_[index_to_delete] = active_touches_[pointer_count_];
+}
+
+MotionEventAura::PointData::PointData()
+    : x(0),
+      y(0),
+      raw_x(0),
+      raw_y(0),
+      touch_id(0),
+      pressure(0),
+      source_device_id(0),
+      touch_major(0),
+      touch_minor(0),
+      orientation(0) {
+}
+
+int MotionEventAura::GetSourceDeviceId(size_t pointer_index) const {
+  DCHECK_LT(pointer_index, pointer_count_);
+  return active_touches_[pointer_index].source_device_id;
+}
+
+void MotionEventAura::AddTouch(const TouchEvent& touch) {
+  if (pointer_count_ == MotionEvent::MAX_TOUCH_POINT_COUNT)
+    return;
+
+  active_touches_[pointer_count_] = GetPointDataFromTouchEvent(touch);
+  pointer_count_++;
+}
+
+
+void MotionEventAura::UpdateTouch(const TouchEvent& touch) {
+  active_touches_[GetIndexFromId(touch.touch_id())] =
+      GetPointDataFromTouchEvent(touch);
+}
+
+void MotionEventAura::UpdateCachedAction(const TouchEvent& touch) {
+  DCHECK(pointer_count_);
+  switch (touch.type()) {
+    case ET_TOUCH_PRESSED:
+      if (pointer_count_ == 1) {
+        cached_action_ = ACTION_DOWN;
+      } else {
+        cached_action_ = ACTION_POINTER_DOWN;
+        cached_action_index_ =
+            static_cast<int>(GetIndexFromId(touch.touch_id()));
+      }
+      break;
+    case ET_TOUCH_RELEASED:
+      if (pointer_count_ == 1) {
+        cached_action_ = ACTION_UP;
+      } else {
+        cached_action_ = ACTION_POINTER_UP;
+        cached_action_index_ =
+            static_cast<int>(GetIndexFromId(touch.touch_id()));
+        DCHECK_LT(cached_action_index_, static_cast<int>(pointer_count_));
+      }
+      break;
+    case ET_TOUCH_CANCELLED:
+      cached_action_ = ACTION_CANCEL;
+      break;
+    case ET_TOUCH_MOVED:
+      cached_action_ = ACTION_MOVE;
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+}
+
+size_t MotionEventAura::GetIndexFromId(int id) const {
+  for (size_t i = 0; i < pointer_count_; ++i) {
+    if (active_touches_[i].touch_id == id)
+      return i;
+  }
+  NOTREACHED();
+  return 0;
+}
+
+}  // namespace ui
diff --git a/ui/events/gestures/motion_event_aura.h b/ui/events/gestures/motion_event_aura.h
new file mode 100644
index 0000000..ec956fc
--- /dev/null
+++ b/ui/events/gestures/motion_event_aura.h
@@ -0,0 +1,102 @@
+// Copyright 2014 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 UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
+#define UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
+
+#include <map>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/event.h"
+#include "ui/events/events_export.h"
+#include "ui/events/gesture_detection/motion_event.h"
+
+namespace ui {
+
+// Implementation of MotionEvent which takes a stream of ui::TouchEvents.
+class EVENTS_EXPORT MotionEventAura : public MotionEvent {
+ public:
+  MotionEventAura();
+  virtual ~MotionEventAura();
+
+  void OnTouch(const TouchEvent& touch);
+
+  // MotionEvent implementation.
+  virtual int GetId() const OVERRIDE;
+  virtual Action GetAction() const OVERRIDE;
+  virtual int GetActionIndex() const OVERRIDE;
+  virtual size_t GetPointerCount() const OVERRIDE;
+  virtual int GetPointerId(size_t pointer_index) const OVERRIDE;
+  virtual float GetX(size_t pointer_index) const OVERRIDE;
+  virtual float GetY(size_t pointer_index) const OVERRIDE;
+  virtual float GetRawX(size_t pointer_index) const OVERRIDE;
+  virtual float GetRawY(size_t pointer_index) const OVERRIDE;
+  virtual float GetTouchMajor(size_t pointer_index) const OVERRIDE;
+  virtual float GetTouchMinor(size_t pointer_index) const OVERRIDE;
+  virtual float GetOrientation(size_t pointer_index) const OVERRIDE;
+  virtual float GetPressure(size_t pointer_index) const OVERRIDE;
+  virtual ToolType GetToolType(size_t pointer_index) const OVERRIDE;
+  virtual int GetButtonState() const OVERRIDE;
+  virtual int GetFlags() const OVERRIDE;
+  virtual base::TimeTicks GetEventTime() const OVERRIDE;
+
+  virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE;
+  virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE;
+
+  int GetSourceDeviceId(size_t pointer_index) const;
+
+  // We can't cleanup removed touch points immediately upon receipt of a
+  // TouchCancel or TouchRelease, as the MotionEvent needs to be able to report
+  // information about those touch events. Once the MotionEvent has been
+  // processed, we call CleanupRemovedTouchPoints to do the required
+  // book-keeping.
+  void CleanupRemovedTouchPoints(const TouchEvent& event);
+
+ private:
+  struct PointData {
+    PointData();
+    float x;
+    float y;
+    float raw_x;
+    float raw_y;
+    int touch_id;
+    float pressure;
+    int source_device_id;
+    float touch_major;
+    float touch_minor;
+    float orientation;
+  };
+
+  MotionEventAura(
+      size_t pointer_count,
+      const base::TimeTicks& last_touch_time,
+      Action cached_action,
+      int cached_action_index,
+      int flags,
+      const PointData (&active_touches)[MotionEvent::MAX_TOUCH_POINT_COUNT]);
+
+  static PointData GetPointDataFromTouchEvent(const TouchEvent& touch);
+  void AddTouch(const TouchEvent& touch);
+  void UpdateTouch(const TouchEvent& touch);
+  void UpdateCachedAction(const TouchEvent& touch);
+  size_t GetIndexFromId(int id) const;
+
+  size_t pointer_count_;
+  base::TimeTicks last_touch_time_;
+  Action cached_action_;
+  // The index of the touch responsible for last ACTION_POINTER_DOWN or
+  // ACTION_POINTER_UP. -1 if no such action has occurred.
+  int cached_action_index_;
+  int flags_;
+
+  // We want constant time indexing by pointer_index, and fast indexing by id.
+  PointData active_touches_[MotionEvent::MAX_TOUCH_POINT_COUNT];
+
+  DISALLOW_COPY_AND_ASSIGN(MotionEventAura);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
diff --git a/ui/events/gestures/motion_event_aura_unittest.cc b/ui/events/gestures/motion_event_aura_unittest.cc
new file mode 100644
index 0000000..6e645dc
--- /dev/null
+++ b/ui/events/gestures/motion_event_aura_unittest.cc
@@ -0,0 +1,423 @@
+// Copyright 2014 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.
+
+// MSVC++ requires this to be set before any other includes to get M_PI.
+#define _USE_MATH_DEFINES
+
+#include <cmath>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/gestures/motion_event_aura.h"
+
+namespace {
+
+ui::TouchEvent TouchWithType(ui::EventType type, int id) {
+  return ui::TouchEvent(
+      type, gfx::PointF(0, 0), id, base::TimeDelta::FromMilliseconds(0));
+}
+
+ui::TouchEvent TouchWithPosition(ui::EventType type,
+                                 int id,
+                                 float x,
+                                 float y,
+                                 float raw_x,
+                                 float raw_y) {
+  ui::TouchEvent event(type,
+                       gfx::PointF(x, y),
+                       0,
+                       id,
+                       base::TimeDelta::FromMilliseconds(0),
+                       0,
+                       0,
+                       0,
+                       0);
+  event.set_root_location(gfx::PointF(raw_x, raw_y));
+  return event;
+}
+
+ui::TouchEvent TouchWithTapParams(ui::EventType type,
+                                 int id,
+                                 float radius_x,
+                                 float radius_y,
+                                 float rotation_angle,
+                                 float pressure) {
+  ui::TouchEvent event(type,
+                       gfx::PointF(1, 1),
+                       0,
+                       id,
+                       base::TimeDelta::FromMilliseconds(0),
+                       radius_x,
+                       radius_y,
+                       rotation_angle,
+                       pressure);
+  event.set_root_location(gfx::PointF(1, 1));
+  return event;
+}
+
+ui::TouchEvent TouchWithTime(ui::EventType type, int id, int ms) {
+  return ui::TouchEvent(
+      type, gfx::PointF(0, 0), id, base::TimeDelta::FromMilliseconds(ms));
+}
+
+base::TimeTicks MsToTicks(int ms) {
+  return base::TimeTicks() + base::TimeDelta::FromMilliseconds(ms);
+}
+
+}  // namespace
+
+namespace ui {
+
+TEST(MotionEventAuraTest, PointerCountAndIds) {
+  // Test that |PointerCount()| returns the correct number of pointers, and ids
+  // are assigned correctly.
+  int ids[] = {4, 6, 1};
+
+  MotionEventAura event;
+  EXPECT_EQ(0U, event.GetPointerCount());
+
+  TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]);
+  event.OnTouch(press0);
+  EXPECT_EQ(1U, event.GetPointerCount());
+
+  EXPECT_EQ(ids[0], event.GetPointerId(0));
+
+  TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]);
+  event.OnTouch(press1);
+  EXPECT_EQ(2U, event.GetPointerCount());
+
+  EXPECT_EQ(ids[0], event.GetPointerId(0));
+  EXPECT_EQ(ids[1], event.GetPointerId(1));
+
+  TouchEvent press2 = TouchWithType(ET_TOUCH_PRESSED, ids[2]);
+  event.OnTouch(press2);
+  EXPECT_EQ(3U, event.GetPointerCount());
+
+  EXPECT_EQ(ids[0], event.GetPointerId(0));
+  EXPECT_EQ(ids[1], event.GetPointerId(1));
+  EXPECT_EQ(ids[2], event.GetPointerId(2));
+
+  TouchEvent release1 = TouchWithType(ET_TOUCH_RELEASED, ids[1]);
+  event.OnTouch(release1);
+  event.CleanupRemovedTouchPoints(release1);
+  EXPECT_EQ(2U, event.GetPointerCount());
+
+  EXPECT_EQ(ids[0], event.GetPointerId(0));
+  EXPECT_EQ(ids[2], event.GetPointerId(1));
+
+  // Test cloning of pointer count and id information.
+  // TODO(mustaq): Make a separate clone test
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  EXPECT_EQ(2U, clone->GetPointerCount());
+  EXPECT_EQ(ids[0], clone->GetPointerId(0));
+  EXPECT_EQ(ids[2], clone->GetPointerId(1));
+
+  TouchEvent release0 = TouchWithType(ET_TOUCH_RELEASED, ids[0]);
+  event.OnTouch(release0);
+  event.CleanupRemovedTouchPoints(release0);
+  EXPECT_EQ(1U, event.GetPointerCount());
+
+  EXPECT_EQ(ids[2], event.GetPointerId(0));
+
+  TouchEvent release2 = TouchWithType(ET_TOUCH_RELEASED, ids[2]);
+  event.OnTouch(release2);
+  event.CleanupRemovedTouchPoints(release2);
+  EXPECT_EQ(0U, event.GetPointerCount());
+}
+
+TEST(MotionEventAuraTest, GetActionIndexAfterRemoval) {
+  // Test that |GetActionIndex()| returns the correct index when points have
+  // been removed.
+  int ids[] = {4, 6, 9};
+
+  MotionEventAura event;
+  EXPECT_EQ(0U, event.GetPointerCount());
+
+  TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]);
+  event.OnTouch(press0);
+  TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]);
+  event.OnTouch(press1);
+  TouchEvent press2 = TouchWithType(ET_TOUCH_PRESSED, ids[2]);
+  event.OnTouch(press2);
+  EXPECT_EQ(3U, event.GetPointerCount());
+
+  TouchEvent release1 = TouchWithType(ET_TOUCH_RELEASED, ids[1]);
+  event.OnTouch(release1);
+  event.CleanupRemovedTouchPoints(release1);
+  EXPECT_EQ(1, event.GetActionIndex());
+  EXPECT_EQ(2U, event.GetPointerCount());
+
+  TouchEvent release2 = TouchWithType(ET_TOUCH_RELEASED, ids[0]);
+  event.OnTouch(release2);
+  event.CleanupRemovedTouchPoints(release2);
+  EXPECT_EQ(0, event.GetActionIndex());
+  EXPECT_EQ(1U, event.GetPointerCount());
+
+  TouchEvent release0 = TouchWithType(ET_TOUCH_RELEASED, ids[2]);
+  event.OnTouch(release0);
+  event.CleanupRemovedTouchPoints(release0);
+  EXPECT_EQ(0U, event.GetPointerCount());
+}
+
+TEST(MotionEventAuraTest, PointerLocations) {
+  // Test that location information is stored correctly.
+  MotionEventAura event;
+
+  const float kRawOffsetX = 11.1f;
+  const float kRawOffsetY = 13.3f;
+
+  int ids[] = {15, 13};
+  float x;
+  float y;
+  float raw_x;
+  float raw_y;
+
+  x = 14.4f;
+  y = 17.3f;
+  raw_x = x + kRawOffsetX;
+  raw_y = y + kRawOffsetY;
+  TouchEvent press0 =
+      TouchWithPosition(ET_TOUCH_PRESSED, ids[0], x, y, raw_x, raw_y);
+  event.OnTouch(press0);
+
+  EXPECT_EQ(1U, event.GetPointerCount());
+  EXPECT_FLOAT_EQ(x, event.GetX(0));
+  EXPECT_FLOAT_EQ(y, event.GetY(0));
+  EXPECT_FLOAT_EQ(raw_x, event.GetRawX(0));
+  EXPECT_FLOAT_EQ(raw_y, event.GetRawY(0));
+
+  x = 17.8f;
+  y = 12.1f;
+  raw_x = x + kRawOffsetX;
+  raw_y = y + kRawOffsetY;
+  TouchEvent press1 =
+      TouchWithPosition(ET_TOUCH_PRESSED, ids[1], x, y, raw_x, raw_y);
+  event.OnTouch(press1);
+
+  EXPECT_EQ(2U, event.GetPointerCount());
+  EXPECT_FLOAT_EQ(x, event.GetX(1));
+  EXPECT_FLOAT_EQ(y, event.GetY(1));
+  EXPECT_FLOAT_EQ(raw_x, event.GetRawX(1));
+  EXPECT_FLOAT_EQ(raw_y, event.GetRawY(1));
+
+  // Test cloning of pointer location information.
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  {
+    const MotionEventAura* raw_clone_aura =
+        static_cast<MotionEventAura*>(clone.get());
+    EXPECT_EQ(2U, raw_clone_aura->GetPointerCount());
+    EXPECT_FLOAT_EQ(x, raw_clone_aura->GetX(1));
+    EXPECT_FLOAT_EQ(y, raw_clone_aura->GetY(1));
+    EXPECT_FLOAT_EQ(raw_x, raw_clone_aura->GetRawX(1));
+    EXPECT_FLOAT_EQ(raw_y, raw_clone_aura->GetRawY(1));
+  }
+
+  x = 27.9f;
+  y = 22.3f;
+  raw_x = x + kRawOffsetX;
+  raw_y = y + kRawOffsetY;
+  TouchEvent move1 =
+      TouchWithPosition(ET_TOUCH_MOVED, ids[1], x, y, raw_x, raw_y);
+  event.OnTouch(move1);
+
+  EXPECT_FLOAT_EQ(x, event.GetX(1));
+  EXPECT_FLOAT_EQ(y, event.GetY(1));
+  EXPECT_FLOAT_EQ(raw_x, event.GetRawX(1));
+  EXPECT_FLOAT_EQ(raw_y, event.GetRawY(1));
+
+  x = 34.6f;
+  y = 23.8f;
+  raw_x = x + kRawOffsetX;
+  raw_y = y + kRawOffsetY;
+  TouchEvent move0 =
+      TouchWithPosition(ET_TOUCH_MOVED, ids[0], x, y, raw_x, raw_y);
+  event.OnTouch(move0);
+
+  EXPECT_FLOAT_EQ(x, event.GetX(0));
+  EXPECT_FLOAT_EQ(y, event.GetY(0));
+  EXPECT_FLOAT_EQ(raw_x, event.GetRawX(0));
+  EXPECT_FLOAT_EQ(raw_y, event.GetRawY(0));
+}
+
+TEST(MotionEventAuraTest, TapParams) {
+  // Test that touch params are stored correctly.
+  MotionEventAura event;
+
+  int ids[] = {15, 13};
+
+  float radius_x;
+  float radius_y;
+  float rotation_angle;
+  float pressure;
+
+  radius_x = 123.45f;
+  radius_y = 67.89f;
+  rotation_angle = 23.f;
+  pressure = 0.123f;
+  TouchEvent press0 = TouchWithTapParams(
+      ET_TOUCH_PRESSED, ids[0], radius_x, radius_y, rotation_angle, pressure);
+  event.OnTouch(press0);
+
+  EXPECT_EQ(1U, event.GetPointerCount());
+  EXPECT_FLOAT_EQ(radius_x, event.GetTouchMajor(0) / 2);
+  EXPECT_FLOAT_EQ(radius_y, event.GetTouchMinor(0) / 2);
+  EXPECT_FLOAT_EQ(rotation_angle, event.GetOrientation(0) * 180 / M_PI + 90);
+  EXPECT_FLOAT_EQ(pressure, event.GetPressure(0));
+
+  radius_x = 67.89f;
+  radius_y = 123.45f;
+  rotation_angle = 46.f;
+  pressure = 0.456f;
+  TouchEvent press1 = TouchWithTapParams(
+      ET_TOUCH_PRESSED, ids[1], radius_x, radius_y, rotation_angle, pressure);
+  event.OnTouch(press1);
+
+  EXPECT_EQ(2U, event.GetPointerCount());
+  EXPECT_FLOAT_EQ(radius_y, event.GetTouchMajor(1) / 2);
+  EXPECT_FLOAT_EQ(radius_x, event.GetTouchMinor(1) / 2);
+  EXPECT_FLOAT_EQ(rotation_angle, event.GetOrientation(1) * 180 / M_PI);
+  EXPECT_FLOAT_EQ(pressure, event.GetPressure(1));
+
+  // Test cloning of tap params
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  {
+    const MotionEventAura* raw_clone_aura =
+        static_cast<MotionEventAura*>(clone.get());
+    EXPECT_EQ(2U, raw_clone_aura->GetPointerCount());
+    EXPECT_FLOAT_EQ(radius_y, raw_clone_aura->GetTouchMajor(1) / 2);
+    EXPECT_FLOAT_EQ(radius_x, raw_clone_aura->GetTouchMinor(1) / 2);
+    EXPECT_FLOAT_EQ(
+        rotation_angle, raw_clone_aura->GetOrientation(1) * 180 / M_PI);
+    EXPECT_FLOAT_EQ(pressure, raw_clone_aura->GetPressure(1));
+  }
+
+  radius_x = 76.98f;
+  radius_y = 321.54f;
+  rotation_angle = 64.f;
+  pressure = 0.654f;
+  TouchEvent move1 = TouchWithTapParams(
+      ET_TOUCH_MOVED, ids[1], radius_x, radius_y, rotation_angle, pressure);
+  event.OnTouch(move1);
+
+  EXPECT_EQ(2U, event.GetPointerCount());
+  EXPECT_FLOAT_EQ(radius_y, event.GetTouchMajor(1) / 2);
+  EXPECT_FLOAT_EQ(radius_x, event.GetTouchMinor(1) / 2);
+  EXPECT_FLOAT_EQ(rotation_angle, event.GetOrientation(1) * 180 / M_PI);
+  EXPECT_FLOAT_EQ(pressure, event.GetPressure(1));
+}
+
+TEST(MotionEventAuraTest, Timestamps) {
+  // Test that timestamp information is stored and converted correctly.
+  MotionEventAura event;
+  int ids[] = {7, 13};
+  int times_in_ms[] = {59436, 60263, 82175};
+
+  TouchEvent press0 = TouchWithTime(
+      ui::ET_TOUCH_PRESSED, ids[0], times_in_ms[0]);
+  event.OnTouch(press0);
+  EXPECT_EQ(MsToTicks(times_in_ms[0]), event.GetEventTime());
+
+  TouchEvent press1 = TouchWithTime(
+      ui::ET_TOUCH_PRESSED, ids[1], times_in_ms[1]);
+  event.OnTouch(press1);
+  EXPECT_EQ(MsToTicks(times_in_ms[1]), event.GetEventTime());
+
+  TouchEvent move0 = TouchWithTime(
+      ui::ET_TOUCH_MOVED, ids[0], times_in_ms[2]);
+  event.OnTouch(move0);
+  EXPECT_EQ(MsToTicks(times_in_ms[2]), event.GetEventTime());
+
+  // Test cloning of timestamp information.
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  EXPECT_EQ(MsToTicks(times_in_ms[2]), clone->GetEventTime());
+}
+
+TEST(MotionEventAuraTest, CachedAction) {
+  // Test that the cached action and cached action index are correct.
+  int ids[] = {4, 6};
+  MotionEventAura event;
+
+  TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]);
+  event.OnTouch(press0);
+  EXPECT_EQ(MotionEvent::ACTION_DOWN, event.GetAction());
+  EXPECT_EQ(1U, event.GetPointerCount());
+
+  TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]);
+  event.OnTouch(press1);
+  EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction());
+  EXPECT_EQ(1, event.GetActionIndex());
+  EXPECT_EQ(2U, event.GetPointerCount());
+
+  // Test cloning of CachedAction information.
+  scoped_ptr<MotionEvent> clone = event.Clone();
+  EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, clone->GetAction());
+  EXPECT_EQ(1, clone->GetActionIndex());
+
+  TouchEvent move0 = TouchWithType(ET_TOUCH_MOVED, ids[0]);
+  event.OnTouch(move0);
+  EXPECT_EQ(MotionEvent::ACTION_MOVE, event.GetAction());
+  EXPECT_EQ(2U, event.GetPointerCount());
+
+  TouchEvent release0 = TouchWithType(ET_TOUCH_RELEASED, ids[0]);
+  event.OnTouch(release0);
+  EXPECT_EQ(MotionEvent::ACTION_POINTER_UP, event.GetAction());
+  EXPECT_EQ(2U, event.GetPointerCount());
+  event.CleanupRemovedTouchPoints(release0);
+  EXPECT_EQ(1U, event.GetPointerCount());
+
+  TouchEvent release1 = TouchWithType(ET_TOUCH_RELEASED, ids[1]);
+  event.OnTouch(release1);
+  EXPECT_EQ(MotionEvent::ACTION_UP, event.GetAction());
+  EXPECT_EQ(1U, event.GetPointerCount());
+  event.CleanupRemovedTouchPoints(release1);
+  EXPECT_EQ(0U, event.GetPointerCount());
+}
+
+TEST(MotionEventAuraTest, Cancel) {
+  int ids[] = {4, 6};
+  MotionEventAura event;
+
+  TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]);
+  event.OnTouch(press0);
+  EXPECT_EQ(MotionEvent::ACTION_DOWN, event.GetAction());
+  EXPECT_EQ(1U, event.GetPointerCount());
+
+  TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]);
+  event.OnTouch(press1);
+  EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction());
+  EXPECT_EQ(1, event.GetActionIndex());
+  EXPECT_EQ(2U, event.GetPointerCount());
+
+  scoped_ptr<MotionEvent> cancel = event.Cancel();
+  EXPECT_EQ(MotionEvent::ACTION_CANCEL, cancel->GetAction());
+  EXPECT_EQ(2U, static_cast<MotionEventAura*>(cancel.get())->GetPointerCount());
+}
+
+TEST(MotionEventAuraTest, ToolType) {
+  MotionEventAura event;
+
+  // For now, all pointers have an unknown tool type.
+  // TODO(jdduke): Expand this test when ui::TouchEvent identifies the source
+  // touch type, crbug.com/404128.
+  event.OnTouch(TouchWithType(ET_TOUCH_PRESSED, 7));
+  ASSERT_EQ(1U, event.GetPointerCount());
+  EXPECT_EQ(MotionEvent::TOOL_TYPE_UNKNOWN, event.GetToolType(0));
+}
+
+TEST(MotionEventAuraTest, Flags) {
+  int ids[] = {7, 11};
+  MotionEventAura event;
+
+  TouchEvent press0 = TouchWithType(ET_TOUCH_PRESSED, ids[0]);
+  press0.set_flags(EF_CONTROL_DOWN);
+  event.OnTouch(press0);
+  EXPECT_EQ(EF_CONTROL_DOWN, event.GetFlags());
+
+  TouchEvent press1 = TouchWithType(ET_TOUCH_PRESSED, ids[1]);
+  press1.set_flags(EF_CONTROL_DOWN | EF_CAPS_LOCK_DOWN);
+  event.OnTouch(press1);
+  EXPECT_EQ(EF_CONTROL_DOWN | EF_CAPS_LOCK_DOWN, event.GetFlags());
+}
+
+}  // namespace ui
diff --git a/ui/events/input_device_event_observer.h b/ui/events/input_device_event_observer.h
new file mode 100644
index 0000000..679f1f4
--- /dev/null
+++ b/ui/events/input_device_event_observer.h
@@ -0,0 +1,20 @@
+// Copyright 2014 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 UI_EVENTS_INPUT_DEVICE_EVENT_OBSERVER_H_
+#define UI_EVENTS_INPUT_DEVICE_EVENT_OBSERVER_H_
+
+namespace ui {
+
+// DeviceDataManager observer used to announce input hotplug events.
+class InputDeviceEventObserver {
+ public:
+  virtual ~InputDeviceEventObserver() {}
+
+  virtual void OnInputDeviceConfigurationChanged() = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_INPUT_DEVICE_EVENT_OBSERVER_H_
diff --git a/ui/events/ipc/BUILD.gn b/ui/events/ipc/BUILD.gn
new file mode 100644
index 0000000..0462410
--- /dev/null
+++ b/ui/events/ipc/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2014 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.
+
+import("//build/config/ui.gni")
+
+component("ipc") {
+  output_name = "events_ipc"
+  sources = [
+    "latency_info_param_traits.cc",
+    "latency_info_param_traits.h",
+  ]
+
+  defines = [ "EVENTS_IMPLEMENTATION" ]
+
+  deps = [
+    "//ipc",
+    "//ui/events",
+  ]
+}
+
diff --git a/ui/events/ipc/OWNERS b/ui/events/ipc/OWNERS
new file mode 100644
index 0000000..6732300
--- /dev/null
+++ b/ui/events/ipc/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+dcheng@chromium.org
+inferno@chromium.org
+jln@chromium.org
+jschuh@chromium.org
+kenrb@chromium.org
+nasko@chromium.org
+palmer@chromium.org
+tsepez@chromium.org
+
+per-file *.gyp*=*
+per-file BUILD.gn=*
diff --git a/ui/events/ipc/events_ipc.gyp b/ui/events/ipc/events_ipc.gyp
new file mode 100644
index 0000000..898bf2f
--- /dev/null
+++ b/ui/events/ipc/events_ipc.gyp
@@ -0,0 +1,30 @@
+# Copyright 2014 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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [
+    {
+      # GN version: //ui/events/ipc
+      'target_name': 'events_ipc',
+      'type': '<(component)',
+      'dependencies': [
+        '<(DEPTH)/base/base.gyp:base',
+        '<(DEPTH)/ipc/ipc.gyp:ipc',
+      ],
+      'defines': [
+        'EVENTS_IMPLEMENTATION',
+      ],
+      'include_dirs': [
+        '../..',
+      ],
+      'sources': [
+        'latency_info_param_traits.cc',
+        'latency_info_param_traits.h',
+      ],
+    },
+  ],
+}
diff --git a/ui/events/ipc/latency_info_param_traits.cc b/ui/events/ipc/latency_info_param_traits.cc
new file mode 100644
index 0000000..0b36c31
--- /dev/null
+++ b/ui/events/ipc/latency_info_param_traits.cc
@@ -0,0 +1,26 @@
+// Copyright 2014 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 "ui/events/ipc/latency_info_param_traits.h"
+
+// Generate param traits write methods.
+#include "ipc/param_traits_write_macros.h"
+namespace IPC {
+#undef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
+#include "ui/events/ipc/latency_info_param_traits.h"
+}  // namespace IPC
+
+// Generate param traits read methods.
+#include "ipc/param_traits_read_macros.h"
+namespace IPC {
+#undef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
+#include "ui/events/ipc/latency_info_param_traits.h"
+}  // namespace IPC
+
+// Generate param traits log methods.
+#include "ipc/param_traits_log_macros.h"
+namespace IPC {
+#undef UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
+#include "ui/events/ipc/latency_info_param_traits.h"
+}  // namespace IPC
diff --git a/ui/events/ipc/latency_info_param_traits.h b/ui/events/ipc/latency_info_param_traits.h
new file mode 100644
index 0000000..289ecb4
--- /dev/null
+++ b/ui/events/ipc/latency_info_param_traits.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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 UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
+#define UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
+
+#include "ipc/ipc_message_macros.h"
+#include "ui/events/events_export.h"
+#include "ui/events/latency_info.h"
+
+#undef IPC_MESSAGE_EXPORT
+#define IPC_MESSAGE_EXPORT EVENTS_EXPORT
+
+IPC_ENUM_TRAITS_MAX_VALUE(ui::LatencyComponentType,
+                          ui::LATENCY_COMPONENT_TYPE_LAST)
+
+IPC_STRUCT_TRAITS_BEGIN(ui::LatencyInfo::LatencyComponent)
+  IPC_STRUCT_TRAITS_MEMBER(sequence_number)
+  IPC_STRUCT_TRAITS_MEMBER(event_time)
+  IPC_STRUCT_TRAITS_MEMBER(event_count)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ui::LatencyInfo::InputCoordinate)
+IPC_STRUCT_TRAITS_MEMBER(x)
+IPC_STRUCT_TRAITS_MEMBER(y)
+IPC_STRUCT_TRAITS_END()
+
+IPC_STRUCT_TRAITS_BEGIN(ui::LatencyInfo)
+  IPC_STRUCT_TRAITS_MEMBER(latency_components)
+  IPC_STRUCT_TRAITS_MEMBER(trace_id)
+  IPC_STRUCT_TRAITS_MEMBER(terminated)
+  IPC_STRUCT_TRAITS_MEMBER(input_coordinates_size)
+  IPC_STRUCT_TRAITS_MEMBER(input_coordinates[0])
+  IPC_STRUCT_TRAITS_MEMBER(input_coordinates[1])
+IPC_STRUCT_TRAITS_END()
+
+#endif // UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
diff --git a/ui/events/keycodes/DEPS b/ui/events/keycodes/DEPS
new file mode 100644
index 0000000..a2ede37
--- /dev/null
+++ b/ui/events/keycodes/DEPS
@@ -0,0 +1,11 @@
+include_rules = [
+  # Remove DEPS allowed by ui/base
+  "-grit",
+  "-jni",
+  "-net",
+  "-skia",
+  "-third_party",
+  "-ui",
+
+  "+ui/events",
+]
diff --git a/ui/events/keycodes/OWNERS b/ui/events/keycodes/OWNERS
new file mode 100644
index 0000000..a49314c
--- /dev/null
+++ b/ui/events/keycodes/OWNERS
@@ -0,0 +1,2 @@
+garykac@chromium.org
+wez@chromium.org
diff --git a/ui/events/keycodes/dom4/DEPS b/ui/events/keycodes/dom4/DEPS
new file mode 100644
index 0000000..b548956
--- /dev/null
+++ b/ui/events/keycodes/dom4/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+ui/events/keycodes/dom4",
+]
diff --git a/ui/events/keycodes/dom4/keycode_converter.cc b/ui/events/keycodes/dom4/keycode_converter.cc
new file mode 100644
index 0000000..b7958dd
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter.cc
@@ -0,0 +1,134 @@
+// Copyright 2013 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 "ui/events/keycodes/dom4/keycode_converter.h"
+
+namespace ui {
+
+namespace {
+
+#if defined(OS_WIN)
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, win, code}
+#elif defined(OS_LINUX)
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, xkb, code}
+#elif defined(OS_MACOSX)
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, mac, code}
+#else
+#define USB_KEYMAP(usb, xkb, win, mac, code) {usb, 0, code}
+#endif
+#include "ui/events/keycodes/dom4/keycode_converter_data.h"
+
+const size_t kKeycodeMapEntries = arraysize(usb_keycode_map);
+
+}  // namespace
+
+// static
+size_t KeycodeConverter::NumKeycodeMapEntriesForTest() {
+  return kKeycodeMapEntries;
+}
+
+// static
+const KeycodeMapEntry* KeycodeConverter::GetKeycodeMapForTest() {
+  return &usb_keycode_map[0];
+}
+
+// static
+uint16_t KeycodeConverter::InvalidNativeKeycode() {
+  return usb_keycode_map[0].native_keycode;
+}
+
+// static
+const char* KeycodeConverter::InvalidKeyboardEventCode() {
+  return "Unidentified";
+}
+
+// static
+const char* KeycodeConverter::NativeKeycodeToCode(uint16_t native_keycode) {
+  for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+    if (usb_keycode_map[i].native_keycode == native_keycode) {
+      if (usb_keycode_map[i].code != NULL)
+        return usb_keycode_map[i].code;
+      break;
+    }
+  }
+  return InvalidKeyboardEventCode();
+}
+
+// static
+uint16_t KeycodeConverter::CodeToNativeKeycode(const char* code) {
+  if (!code ||
+      strcmp(code, InvalidKeyboardEventCode()) == 0) {
+    return InvalidNativeKeycode();
+  }
+
+  for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+    if (usb_keycode_map[i].code &&
+        strcmp(usb_keycode_map[i].code, code) == 0) {
+      return usb_keycode_map[i].native_keycode;
+    }
+  }
+  return InvalidNativeKeycode();
+}
+
+// USB keycodes
+// Note that USB keycodes are not part of any web standard.
+// Please don't use USB keycodes in new code.
+
+// static
+uint16_t KeycodeConverter::InvalidUsbKeycode() {
+  return usb_keycode_map[0].usb_keycode;
+}
+
+// static
+uint16_t KeycodeConverter::UsbKeycodeToNativeKeycode(uint32_t usb_keycode) {
+  // Deal with some special-cases that don't fit the 1:1 mapping.
+  if (usb_keycode == 0x070032) // non-US hash.
+    usb_keycode = 0x070031; // US backslash.
+#if defined(OS_MACOSX)
+  if (usb_keycode == 0x070046) // PrintScreen.
+    usb_keycode = 0x070068; // F13.
+#endif
+
+  for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+    if (usb_keycode_map[i].usb_keycode == usb_keycode)
+      return usb_keycode_map[i].native_keycode;
+  }
+  return InvalidNativeKeycode();
+}
+
+// static
+uint32_t KeycodeConverter::NativeKeycodeToUsbKeycode(uint16_t native_keycode) {
+  for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+    if (usb_keycode_map[i].native_keycode == native_keycode)
+      return usb_keycode_map[i].usb_keycode;
+  }
+  return InvalidUsbKeycode();
+}
+
+// static
+const char* KeycodeConverter::UsbKeycodeToCode(uint32_t usb_keycode) {
+  for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+    if (usb_keycode_map[i].usb_keycode == usb_keycode)
+      return usb_keycode_map[i].code;
+  }
+  return InvalidKeyboardEventCode();
+}
+
+// static
+uint32_t KeycodeConverter::CodeToUsbKeycode(const char* code) {
+  if (!code ||
+      strcmp(code, InvalidKeyboardEventCode()) == 0) {
+    return InvalidUsbKeycode();
+  }
+
+  for (size_t i = 0; i < kKeycodeMapEntries; ++i) {
+    if (usb_keycode_map[i].code &&
+        strcmp(usb_keycode_map[i].code, code) == 0) {
+      return usb_keycode_map[i].usb_keycode;
+    }
+  }
+  return InvalidUsbKeycode();
+}
+
+}  // namespace ui
diff --git a/ui/events/keycodes/dom4/keycode_converter.h b/ui/events/keycodes/dom4/keycode_converter.h
new file mode 100644
index 0000000..a52d086
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter.h
@@ -0,0 +1,84 @@
+// Copyright 2013 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 UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_H_
+#define UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_H_
+
+#include <stdint.h>
+#include "base/basictypes.h"
+
+// For reference, the W3C UI Event spec is located at:
+// http://www.w3.org/TR/uievents/
+
+namespace ui {
+
+// This structure is used to define the keycode mapping table.
+// It is defined here because the unittests need access to it.
+typedef struct {
+  // USB keycode:
+  //  Upper 16-bits: USB Usage Page.
+  //  Lower 16-bits: USB Usage Id: Assigned ID within this usage page.
+  uint32_t usb_keycode;
+
+  // Contains one of the following:
+  //  On Linux: XKB scancode
+  //  On Windows: Windows OEM scancode
+  //  On Mac: Mac keycode
+  uint16_t native_keycode;
+
+  // The UIEvents (aka: DOM4Events) |code| value as defined in:
+  // https://dvcs.w3.org/hg/d4e/raw-file/tip/source_respec.htm
+  const char* code;
+} KeycodeMapEntry;
+
+// A class to convert between the current platform's native keycode (scancode)
+// and platform-neutral |code| values (as defined in the W3C UI Events
+// spec (http://www.w3.org/TR/uievents/).
+class KeycodeConverter {
+ public:
+  // Return the value that identifies an invalid native keycode.
+  static uint16_t InvalidNativeKeycode();
+
+  // Return the string that indentifies an invalid UI Event |code|.
+  // The returned pointer references a static global string.
+  static const char* InvalidKeyboardEventCode();
+
+  // Convert a native (Mac/Win/Linux) keycode into the |code| string.
+  // The returned pointer references a static global string.
+  static const char* NativeKeycodeToCode(uint16_t native_keycode);
+
+  // Convert a UI Events |code| string value into a native keycode.
+  static uint16_t CodeToNativeKeycode(const char* code);
+
+  // The following methods relate to USB keycodes.
+  // Note that USB keycodes are not part of any web standard.
+  // Please don't use USB keycodes in new code.
+
+  // Return the value that identifies an invalid USB keycode.
+  static uint16_t InvalidUsbKeycode();
+
+  // Convert a USB keycode into an equivalent platform native keycode.
+  static uint16_t UsbKeycodeToNativeKeycode(uint32_t usb_keycode);
+
+  // Convert a platform native keycode into an equivalent USB keycode.
+  static uint32_t NativeKeycodeToUsbKeycode(uint16_t native_keycode);
+
+  // Convert a USB keycode into the string with the DOM3 |code| value.
+  // The returned pointer references a static global string.
+  static const char* UsbKeycodeToCode(uint32_t usb_keycode);
+
+  // Convert a DOM3 Event |code| string into a USB keycode value.
+  static uint32_t CodeToUsbKeycode(const char* code);
+
+  // Static methods to support testing.
+  static size_t NumKeycodeMapEntriesForTest();
+  static const KeycodeMapEntry* GetKeycodeMapForTest();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(KeycodeConverter);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_H_
diff --git a/ui/events/keycodes/dom4/keycode_converter_data.h b/ui/events/keycodes/dom4/keycode_converter_data.h
new file mode 100644
index 0000000..9cdeb5f
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter_data.h
@@ -0,0 +1,379 @@
+// Copyright 2013 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 UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_DATA_H_
+#define UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_DATA_H_
+
+// Data in this file was created by referencing:
+//  USB HID Usage Tables (v1.11) 27 June 2001
+//  HIToolbox/Events.h (Mac)
+
+const KeycodeMapEntry usb_keycode_map[] = {
+
+  //            USB      XKB     Win     Mac   Code
+  USB_KEYMAP(0x000000, 0x0000, 0x0000, 0xffff, NULL),  // Invalid
+
+  // =========================================
+  // USB Usage Page 0x01: Generic Desktop Page
+  // =========================================
+
+  // Sleep could be encoded as USB#0c0032, but there's no corresponding WakeUp
+  // in the 0x0c USB page.
+
+  //            USB      XKB     Win     Mac
+  USB_KEYMAP(0x010082, 0x0096, 0x0000, 0xffff, NULL),  // SystemSleep
+  USB_KEYMAP(0x010083, 0x0097, 0x0000, 0xffff, NULL),  // SystemWakeUp
+
+  // =========================================
+  // USB Usage Page 0x07: Keyboard/Keypad Page
+  // =========================================
+
+  // TODO(garykac):
+  // XKB#005c ISO Level3 Shift (AltGr)
+  // XKB#005e <>||
+  // XKB#006d Linefeed
+  // XKB#008a SunProps cf. USB#0700a3 CrSel/Props
+  // XKB#008e SunOpen
+  // Mac#003f kVK_Function
+  // Mac#000a kVK_ISO_Section (ISO keyboards only)
+  // Mac#0066 kVK_JIS_Eisu (USB#07008a Henkan?)
+
+  //            USB      XKB     Win     Mac
+  USB_KEYMAP(0x070000, 0x0000, 0x0000, 0xffff, NULL),    // Reserved
+  USB_KEYMAP(0x070001, 0x0000, 0x0000, 0xffff, NULL),    // ErrorRollOver
+  USB_KEYMAP(0x070002, 0x0000, 0x0000, 0xffff, NULL),    // POSTFail
+  USB_KEYMAP(0x070003, 0x0000, 0x0000, 0xffff, NULL),    // ErrorUndefined
+  USB_KEYMAP(0x070004, 0x0026, 0x001e, 0x0000, "KeyA"),  // aA
+  USB_KEYMAP(0x070005, 0x0038, 0x0030, 0x000b, "KeyB"),  // bB
+  USB_KEYMAP(0x070006, 0x0036, 0x002e, 0x0008, "KeyC"),  // cC
+  USB_KEYMAP(0x070007, 0x0028, 0x0020, 0x0002, "KeyD"),  // dD
+
+  USB_KEYMAP(0x070008, 0x001a, 0x0012, 0x000e, "KeyE"),  // eEo
+  USB_KEYMAP(0x070009, 0x0029, 0x0021, 0x0003, "KeyF"),  // fF
+  USB_KEYMAP(0x07000a, 0x002a, 0x0022, 0x0005, "KeyG"),  // gG
+  USB_KEYMAP(0x07000b, 0x002b, 0x0023, 0x0004, "KeyH"),  // hH
+  USB_KEYMAP(0x07000c, 0x001f, 0x0017, 0x0022, "KeyI"),  // iI
+  USB_KEYMAP(0x07000d, 0x002c, 0x0024, 0x0026, "KeyJ"),  // jJ
+  USB_KEYMAP(0x07000e, 0x002d, 0x0025, 0x0028, "KeyK"),  // kK
+  USB_KEYMAP(0x07000f, 0x002e, 0x0026, 0x0025, "KeyL"),  // lL
+
+  USB_KEYMAP(0x070010, 0x003a, 0x0032, 0x002e, "KeyM"),  // mM
+  USB_KEYMAP(0x070011, 0x0039, 0x0031, 0x002d, "KeyN"),  // nN
+  USB_KEYMAP(0x070012, 0x0020, 0x0018, 0x001f, "KeyO"),  // oO
+  USB_KEYMAP(0x070013, 0x0021, 0x0019, 0x0023, "KeyP"),  // pP
+  USB_KEYMAP(0x070014, 0x0018, 0x0010, 0x000c, "KeyQ"),  // qQ
+  USB_KEYMAP(0x070015, 0x001b, 0x0013, 0x000f, "KeyR"),  // rR
+  USB_KEYMAP(0x070016, 0x0027, 0x001f, 0x0001, "KeyS"),  // sS
+  USB_KEYMAP(0x070017, 0x001c, 0x0014, 0x0011, "KeyT"),  // tT
+
+  USB_KEYMAP(0x070018, 0x001e, 0x0016, 0x0020, "KeyU"),  // uU
+  USB_KEYMAP(0x070019, 0x0037, 0x002f, 0x0009, "KeyV"),  // vV
+  USB_KEYMAP(0x07001a, 0x0019, 0x0011, 0x000d, "KeyW"),  // wW
+  USB_KEYMAP(0x07001b, 0x0035, 0x002d, 0x0007, "KeyX"),  // xX
+  USB_KEYMAP(0x07001c, 0x001d, 0x0015, 0x0010, "KeyY"),  // yY
+  USB_KEYMAP(0x07001d, 0x0034, 0x002c, 0x0006, "KeyZ"),  // zZ
+  USB_KEYMAP(0x07001e, 0x000a, 0x0002, 0x0012, "Digit1"),  // 1!
+  USB_KEYMAP(0x07001f, 0x000b, 0x0003, 0x0013, "Digit2"),  // 2@
+
+  USB_KEYMAP(0x070020, 0x000c, 0x0004, 0x0014, "Digit3"),  // 3#
+  USB_KEYMAP(0x070021, 0x000d, 0x0005, 0x0015, "Digit4"),  // 4$
+  USB_KEYMAP(0x070022, 0x000e, 0x0006, 0x0017, "Digit5"),  // 5%
+  USB_KEYMAP(0x070023, 0x000f, 0x0007, 0x0016, "Digit6"),  // 6^
+  USB_KEYMAP(0x070024, 0x0010, 0x0008, 0x001a, "Digit7"),  // 7&
+  USB_KEYMAP(0x070025, 0x0011, 0x0009, 0x001c, "Digit8"),  // 8*
+  USB_KEYMAP(0x070026, 0x0012, 0x000a, 0x0019, "Digit9"),  // 9(
+  USB_KEYMAP(0x070027, 0x0013, 0x000b, 0x001d, "Digit0"),  // 0)
+
+  USB_KEYMAP(0x070028, 0x0024, 0x001c, 0x0024, "Enter"),
+  USB_KEYMAP(0x070029, 0x0009, 0x0001, 0x0035, "Escape"),
+  USB_KEYMAP(0x07002a, 0x0016, 0x000e, 0x0033, "Backspace"),
+  USB_KEYMAP(0x07002b, 0x0017, 0x000f, 0x0030, "Tab"),
+  USB_KEYMAP(0x07002c, 0x0041, 0x0039, 0x0031, "Space"),      // Spacebar
+  USB_KEYMAP(0x07002d, 0x0014, 0x000c, 0x001b, "Minus"),      // -_
+  USB_KEYMAP(0x07002e, 0x0015, 0x000d, 0x0018, "Equal"),      // =+
+  USB_KEYMAP(0x07002f, 0x0022, 0x001a, 0x0021, "BracketLeft"),// [{
+
+  USB_KEYMAP(0x070030, 0x0023, 0x001b, 0x001e, "BracketRight"),  // ]}
+  USB_KEYMAP(0x070031, 0x0033, 0x002b, 0x002a, "Backslash"),     // \| (US keyboard only)
+  // USB#070032 never appears on keyboards that have USB#070031.
+  // Platforms use the same scancode as for the two keys.
+  // The keycap varies on international keyboards:
+  //   Dan: '*  Dutch: <>  Ger: #'  UK: #~
+  // TODO(garykac): Verify Mac intl keyboard.
+  //USB_KEYMAP(0x070032, 0x0033, 0x002b, 0x002a, "IntlHash"),  // #~ (Non-US)
+  USB_KEYMAP(0x070033, 0x002f, 0x0027, 0x0029, "Semicolon"),  // ;:
+  USB_KEYMAP(0x070034, 0x0030, 0x0028, 0x0027, "Quote"),      // '"
+  USB_KEYMAP(0x070035, 0x0031, 0x0029, 0x0032, "Backquote"),  // `~
+  USB_KEYMAP(0x070036, 0x003b, 0x0033, 0x002b, "Comma"),      // ,<
+  USB_KEYMAP(0x070037, 0x003c, 0x0034, 0x002f, "Period"),     // .>
+
+  USB_KEYMAP(0x070038, 0x003d, 0x0035, 0x002c, "Slash"),      // /?
+  // TODO(garykac): CapsLock requires special handling for each platform.
+  USB_KEYMAP(0x070039, 0x0042, 0x003a, 0x0039, "CapsLock"),
+  USB_KEYMAP(0x07003a, 0x0043, 0x003b, 0x007a, "F1"),
+  USB_KEYMAP(0x07003b, 0x0044, 0x003c, 0x0078, "F2"),
+  USB_KEYMAP(0x07003c, 0x0045, 0x003d, 0x0063, "F3"),
+  USB_KEYMAP(0x07003d, 0x0046, 0x003e, 0x0076, "F4"),
+  USB_KEYMAP(0x07003e, 0x0047, 0x003f, 0x0060, "F5"),
+  USB_KEYMAP(0x07003f, 0x0048, 0x0040, 0x0061, "F6"),
+
+  USB_KEYMAP(0x070040, 0x0049, 0x0041, 0x0062, "F7"),
+  USB_KEYMAP(0x070041, 0x004a, 0x0042, 0x0064, "F8"),
+  USB_KEYMAP(0x070042, 0x004b, 0x0043, 0x0065, "F9"),
+  USB_KEYMAP(0x070043, 0x004c, 0x0044, 0x006d, "F10"),
+  USB_KEYMAP(0x070044, 0x005f, 0x0057, 0x0067, "F11"),
+  USB_KEYMAP(0x070045, 0x0060, 0x0058, 0x006f, "F12"),
+  // PrintScreen is effectively F13 on Mac OS X.
+  USB_KEYMAP(0x070046, 0x006b, 0xe037, 0xffff, "PrintScreen"),
+  USB_KEYMAP(0x070047, 0x004e, 0x0046, 0xffff, "ScrollLock"),
+
+  USB_KEYMAP(0x070048, 0x007f, 0x0000, 0xffff, "Pause"),
+  // Labeled "Help/Insert" on Mac.
+  USB_KEYMAP(0x070049, 0x0076, 0xe052, 0x0072, "Insert"),
+  USB_KEYMAP(0x07004a, 0x006e, 0xe047, 0x0073, "Home"),
+  USB_KEYMAP(0x07004b, 0x0070, 0xe049, 0x0074, "PageUp"),
+  // Delete (Forward Delete)
+  USB_KEYMAP(0x07004c, 0x0077, 0xe053, 0x0075, "Delete"),
+  USB_KEYMAP(0x07004d, 0x0073, 0xe04f, 0x0077, "End"),
+  USB_KEYMAP(0x07004e, 0x0075, 0xe051, 0x0079, "PageDown"),
+  USB_KEYMAP(0x07004f, 0x0072, 0xe04d, 0x007c, "ArrowRight"),
+
+  USB_KEYMAP(0x070050, 0x0071, 0xe04b, 0x007b, "ArrowLeft"),
+  USB_KEYMAP(0x070051, 0x0074, 0xe050, 0x007d, "ArrowDown"),
+  USB_KEYMAP(0x070052, 0x006f, 0xe048, 0x007e, "ArrowUp"),
+  USB_KEYMAP(0x070053, 0x004d, 0x0045, 0x0047, "NumLock"),       // Keypad_NumLock Clear
+  USB_KEYMAP(0x070054, 0x006a, 0xe035, 0x004b, "NumpadDivide"),  // Keypad_/
+  USB_KEYMAP(0x070055, 0x003f, 0x0037, 0x0043, "NumpadMultiply"),// Keypad_*
+  USB_KEYMAP(0x070056, 0x0052, 0x004a, 0x004e, "NumpadSubtract"),// Keypad_-
+  USB_KEYMAP(0x070057, 0x0056, 0x004e, 0x0045, "NumpadAdd"),     // Keypad_+
+
+  USB_KEYMAP(0x070058, 0x0068, 0xe01c, 0x004c, "NumpadEnter"),   // Keypad_Enter
+  USB_KEYMAP(0x070059, 0x0057, 0x004f, 0x0053, "Numpad1"),  // Keypad_1 End
+  USB_KEYMAP(0x07005a, 0x0058, 0x0050, 0x0054, "Numpad2"),  // Keypad_2 DownArrow
+  USB_KEYMAP(0x07005b, 0x0059, 0x0051, 0x0055, "Numpad3"),  // Keypad_3 PageDown
+  USB_KEYMAP(0x07005c, 0x0053, 0x004b, 0x0056, "Numpad4"),  // Keypad_4 LeftArrow
+  USB_KEYMAP(0x07005d, 0x0054, 0x004c, 0x0057, "Numpad5"),  // Keypad_5
+  USB_KEYMAP(0x07005e, 0x0055, 0x004d, 0x0058, "Numpad6"),  // Keypad_6 RightArrow
+  USB_KEYMAP(0x07005f, 0x004f, 0x0047, 0x0059, "Numpad7"),  // Keypad_7 Home
+
+  USB_KEYMAP(0x070060, 0x0050, 0x0048, 0x005b, "Numpad8"),  // Keypad_8 UpArrow
+  USB_KEYMAP(0x070061, 0x0051, 0x0049, 0x005c, "Numpad9"),  // Keypad_9 PageUp
+  USB_KEYMAP(0x070062, 0x005a, 0x0052, 0x0052, "Numpad0"),  // Keypad_0 Insert
+  USB_KEYMAP(0x070063, 0x005b, 0x0053, 0x0041, "NumpadDecimal"),  // Keypad_. Delete
+  // USB#070064 is not present on US keyboard.
+  // This key is typically located near LeftShift key.
+  // The keycap varies on international keyboards:
+  //   Dan: <> Dutch: ][ Ger: <> UK: \|
+  USB_KEYMAP(0x070064, 0x005e, 0x0056, 0x000a, "IntlBackslash"),  // Non-US \|
+  // AppMenu (next to RWin key)
+  USB_KEYMAP(0x070065, 0x0087, 0xe05d, 0x006e, "ContextMenu"),
+  USB_KEYMAP(0x070066, 0x007c, 0x0000, 0xffff, "Power"),
+  USB_KEYMAP(0x070067, 0x007d, 0x0000, 0x0051, "NumpadEqual"),  // Keypad_=
+
+  USB_KEYMAP(0x070068, 0x0000, 0x005b, 0x0069, "F13"),
+  USB_KEYMAP(0x070069, 0x0000, 0x005c, 0x006b, "F14"),
+  USB_KEYMAP(0x07006a, 0x0000, 0x005d, 0x0071, "F15"),
+  USB_KEYMAP(0x07006b, 0x0000, 0x0063, 0x006a, "F16"),
+  USB_KEYMAP(0x07006c, 0x0000, 0x0064, 0x0040, "F17"),
+  USB_KEYMAP(0x07006d, 0x0000, 0x0065, 0x004f, "F18"),
+  USB_KEYMAP(0x07006e, 0x0000, 0x0066, 0x0050, "F19"),
+  USB_KEYMAP(0x07006f, 0x0000, 0x0067, 0x005a, "F20"),
+
+  USB_KEYMAP(0x070070, 0x0000, 0x0068, 0xffff, "F21"),
+  USB_KEYMAP(0x070071, 0x0000, 0x0069, 0xffff, "F22"),
+  USB_KEYMAP(0x070072, 0x0000, 0x006a, 0xffff, "F23"),
+  USB_KEYMAP(0x070073, 0x0000, 0x006b, 0xffff, "F24"),
+  USB_KEYMAP(0x070074, 0x0000, 0x0000, 0xffff, NULL),  // Execute
+  USB_KEYMAP(0x070075, 0x0092, 0xe03b, 0xffff, "Help"),
+  USB_KEYMAP(0x070076, 0x0093, 0x0000, 0xffff, NULL),  // Menu
+  //USB_KEYMAP(0x070077, 0x0000, 0x0000, 0xffff, NULL),  // Select
+
+  //USB_KEYMAP(0x070078, 0x0000, 0x0000, 0xffff, NULL),  // Stop
+  USB_KEYMAP(0x070079, 0x0089, 0x0000, 0xffff, NULL),  // Again (Redo)
+  USB_KEYMAP(0x07007a, 0x008b, 0xe008, 0xffff, "Undo"),
+  USB_KEYMAP(0x07007b, 0x0091, 0xe017, 0xffff, "Cut"),
+  USB_KEYMAP(0x07007c, 0x008d, 0xe018, 0xffff, "Copy"),
+  USB_KEYMAP(0x07007d, 0x008f, 0xe00a, 0xffff, "Paste"),
+  USB_KEYMAP(0x07007e, 0x0090, 0x0000, 0xffff, NULL),  // Find
+  USB_KEYMAP(0x07007f, 0x0079, 0xe020, 0x004a, "VolumeMute"),
+
+  USB_KEYMAP(0x070080, 0x007b, 0xe030, 0x0048, "VolumeUp"),
+  USB_KEYMAP(0x070081, 0x007a, 0xe02e, 0x0049, "VolumeDown"),
+  //USB_KEYMAP(0x070082, 0x0000, 0x0000, 0xffff, NULL),  // LockingCapsLock
+  //USB_KEYMAP(0x070083, 0x0000, 0x0000, 0xffff, NULL),  // LockingNumLock
+  //USB_KEYMAP(0x070084, 0x0000, 0x0000, 0xffff, NULL),  // LockingScrollLock
+  // USB#070085 is used as Brazilian Keypad_.
+  USB_KEYMAP(0x070085, 0x0000, 0x0000, 0x005f, "NumpadComma"),  // Keypad_Comma
+
+  // International1
+  // USB#070086 is used on AS/400 keyboards. Standard Keypad_= is USB#070067.
+  //USB_KEYMAP(0x070086, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_=
+  // USB#070087 is used for Brazilian /? and Japanese _ 'ro'.
+  USB_KEYMAP(0x070087, 0x0061, 0x0000, 0x005e, "IntlRo"),
+  // International2
+  // USB#070088 is used as Japanese Hiragana/Katakana key.
+  USB_KEYMAP(0x070088, 0x0065, 0x0000, 0x0068, "KanaMode"),
+  // International3
+  // USB#070089 is used as Japanese Yen key.
+  USB_KEYMAP(0x070089, 0x0084, 0x007d, 0x005d, "IntlYen"),
+  // International4
+  // USB#07008a is used as Japanese Henkan (Convert) key.
+  USB_KEYMAP(0x07008a, 0x0064, 0x0000, 0xffff, "Convert"),
+  // International5
+  // USB#07008b is used as Japanese Muhenkan (No-convert) key.
+  USB_KEYMAP(0x07008b, 0x0066, 0x0000, 0xffff, "NoConvert"),
+  //USB_KEYMAP(0x07008c, 0x0000, 0x0000, 0xffff, NULL),  // International6
+  //USB_KEYMAP(0x07008d, 0x0000, 0x0000, 0xffff, NULL),  // International7
+  //USB_KEYMAP(0x07008e, 0x0000, 0x0000, 0xffff, NULL),  // International8
+  //USB_KEYMAP(0x07008f, 0x0000, 0x0000, 0xffff, NULL),  // International9
+
+  // LANG1
+  // USB#070090 is used as Korean Hangul/English toggle key.
+  USB_KEYMAP(0x070090, 0x0082, 0x0000, 0xffff, "HangulMode"),
+  // LANG2
+  // USB#070091 is used as Korean Hanja conversion key.
+  USB_KEYMAP(0x070091, 0x0083, 0x0000, 0xffff, "Hanja"),
+  // LANG3
+  // USB#070092 is used as Japanese Katakana key.
+  USB_KEYMAP(0x070092, 0x0062, 0x0000, 0xffff, NULL),
+  // LANG4
+  // USB#070093 is used as Japanese Hiragana key.
+  USB_KEYMAP(0x070093, 0x0063, 0x0000, 0xffff, NULL),
+  // LANG5
+  // USB#070094 is used as Japanese Zenkaku/Hankaku (Fullwidth/halfwidth) key.
+  //USB_KEYMAP(0x070094, 0x0000, 0x0000, 0xffff, NULL),
+  //USB_KEYMAP(0x070095, 0x0000, 0x0000, 0xffff, NULL),  // LANG6
+  //USB_KEYMAP(0x070096, 0x0000, 0x0000, 0xffff, NULL),  // LANG7
+  //USB_KEYMAP(0x070097, 0x0000, 0x0000, 0xffff, NULL),  // LANG8
+  //USB_KEYMAP(0x070098, 0x0000, 0x0000, 0xffff, NULL),  // LANG9
+
+  //USB_KEYMAP(0x070099, 0x0000, 0x0000, 0xffff, NULL),  // AlternateErase
+  //USB_KEYMAP(0x07009a, 0x0000, 0x0000, 0xffff, NULL),  // SysReq/Attention
+  USB_KEYMAP(0x07009b, 0x0088, 0x0000, 0xffff, "Cancel"),
+  //USB_KEYMAP(0x07009c, 0x0000, 0x0000, 0xffff, NULL),  // Clear
+  //USB_KEYMAP(0x07009d, 0x0000, 0x0000, 0xffff, NULL),  // Prior
+  //USB_KEYMAP(0x07009e, 0x0000, 0x0000, 0xffff, NULL),  // Return
+  //USB_KEYMAP(0x07009f, 0x0000, 0x0000, 0xffff, NULL),  // Separator
+
+  //USB_KEYMAP(0x0700a0, 0x0000, 0x0000, 0xffff, NULL),  // Out
+  //USB_KEYMAP(0x0700a1, 0x0000, 0x0000, 0xffff, NULL),  // Oper
+  //USB_KEYMAP(0x0700a2, 0x0000, 0x0000, 0xffff, NULL),  // Clear/Again
+  //USB_KEYMAP(0x0700a3, 0x0000, 0x0000, 0xffff, NULL),  // CrSel/Props
+  //USB_KEYMAP(0x0700a4, 0x0000, 0x0000, 0xffff, NULL),  // ExSel
+
+  //USB_KEYMAP(0x0700b0, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_00
+  //USB_KEYMAP(0x0700b1, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_000
+  //USB_KEYMAP(0x0700b2, 0x0000, 0x0000, 0xffff, NULL),  // ThousandsSeparator
+  //USB_KEYMAP(0x0700b3, 0x0000, 0x0000, 0xffff, NULL),  // DecimalSeparator
+  //USB_KEYMAP(0x0700b4, 0x0000, 0x0000, 0xffff, NULL),  // CurrencyUnit
+  //USB_KEYMAP(0x0700b5, 0x0000, 0x0000, 0xffff, NULL),  // CurrencySubunit
+  USB_KEYMAP(0x0700b6, 0x00bb, 0x0000, 0xffff, "NumpadParenLeft"),   // Keypad_(
+  USB_KEYMAP(0x0700b7, 0x00bc, 0x0000, 0xffff, "NumpadParenRight"),  // Keypad_)
+
+  //USB_KEYMAP(0x0700b8, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_{
+  //USB_KEYMAP(0x0700b9, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_}
+  //USB_KEYMAP(0x0700ba, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Tab
+  //USB_KEYMAP(0x0700bb, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Backspace
+  //USB_KEYMAP(0x0700bc, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_A
+  //USB_KEYMAP(0x0700bd, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_B
+  //USB_KEYMAP(0x0700be, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_C
+  //USB_KEYMAP(0x0700bf, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_D
+
+  //USB_KEYMAP(0x0700c0, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_E
+  //USB_KEYMAP(0x0700c1, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_F
+  //USB_KEYMAP(0x0700c2, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Xor
+  //USB_KEYMAP(0x0700c3, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_^
+  //USB_KEYMAP(0x0700c4, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_%
+  //USB_KEYMAP(0x0700c5, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_<
+  //USB_KEYMAP(0x0700c6, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_>
+  //USB_KEYMAP(0x0700c7, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_&
+
+  //USB_KEYMAP(0x0700c8, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_&&
+  //USB_KEYMAP(0x0700c9, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_|
+  //USB_KEYMAP(0x0700ca, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_||
+  //USB_KEYMAP(0x0700cb, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_:
+  //USB_KEYMAP(0x0700cc, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_#
+  //USB_KEYMAP(0x0700cd, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Space
+  //USB_KEYMAP(0x0700ce, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_@
+  //USB_KEYMAP(0x0700cf, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_!
+
+  //USB_KEYMAP(0x0700d0, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemoryStore
+  //USB_KEYMAP(0x0700d1, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemoryRecall
+  //USB_KEYMAP(0x0700d2, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemoryClear
+  //USB_KEYMAP(0x0700d3, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemoryAdd
+  //USB_KEYMAP(0x0700d4, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemorySubtract
+  //USB_KEYMAP(0x0700d5, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemoryMultiply
+  //USB_KEYMAP(0x0700d6, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_MemoryDivide
+  USB_KEYMAP(0x0700d7, 0x007e, 0x0000, 0xffff, "NumpadChangeSign"),  // Keypad_+/-
+
+  //USB_KEYMAP(0x0700d8, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Clear
+  //USB_KEYMAP(0x0700d9, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_ClearEntry
+  //USB_KEYMAP(0x0700da, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Binary
+  //USB_KEYMAP(0x0700db, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Octal
+  USB_KEYMAP(0x0700dc, 0x0081, 0x0000, 0xffff, NULL),  // Keypad_Decimal
+  //USB_KEYMAP(0x0700dd, 0x0000, 0x0000, 0xffff, NULL),  // Keypad_Hexadecimal
+  // USB#0700de - #0700df are reserved.
+
+  USB_KEYMAP(0x0700e0, 0x0025, 0x001d, 0x003b, "ControlLeft"),
+  USB_KEYMAP(0x0700e1, 0x0032, 0x002a, 0x0038, "ShiftLeft"),
+  USB_KEYMAP(0x0700e2, 0x0040, 0x0038, 0x003a, "AltLeft"),   // LeftAlt/Option
+  USB_KEYMAP(0x0700e3, 0x0085, 0xe05b, 0x0037, "OSLeft"),    // LeftGUI/Super/Win/Cmd
+  USB_KEYMAP(0x0700e4, 0x0069, 0xe01d, 0x003e, "ControlRight"),
+  USB_KEYMAP(0x0700e5, 0x003e, 0x0036, 0x003c, "ShiftRight"),
+  USB_KEYMAP(0x0700e6, 0x006c, 0xe038, 0x003d, "AltRight"),  // RightAlt/Option
+  USB_KEYMAP(0x0700e7, 0x0086, 0xe05c, 0x0036, "OSRight"),   // RightGUI/Super/Win/Cmd
+
+  // USB#0700e8 - #07ffff are reserved
+
+  // ==================================
+  // USB Usage Page 0x0c: Consumer Page
+  // ==================================
+  // AL = Application Launch
+  // AC = Application Control
+
+  // TODO(garykac): Many XF86 keys have multiple scancodes mapping to them.
+  // We need to map all of these into a canonical USB scancode without
+  // confusing the reverse-lookup - most likely by simply returning the first
+  // found match.
+
+  // TODO(garykac): Find appropriate mappings for:
+  // Win#e06b LaunchApp1 (My Computer?)
+  // Win#e021 LaunchApp2 (Calculator?)
+  // Win#e03c Music - USB#0c0193 is AL_AVCapturePlayback
+  // Win#e06d Video - USB#0c0193 is AL_AVCapturePlayback
+  // Win#e064 Pictures
+  // XKB#0080 XF86LaunchA
+  // XKB#0097 XF86WakeUp
+  // XKB#0099 XF86Send
+  // XKB#009b XF86Xfer
+  // XKB#009c XF86Launch1
+  // XKB#009d XF86Launch2
+  // XKB... remaining XF86 keys
+
+  //            USB      XKB     Win     Mac
+  USB_KEYMAP(0x0c00b5, 0x0000, 0xe019, 0xffff, "MediaTrackNext"),
+  USB_KEYMAP(0x0c00b6, 0x0000, 0xe010, 0xffff, "MediaTrackPrevious"),
+  USB_KEYMAP(0x0c00b7, 0x0000, 0xe024, 0xffff, "MediaStop"),
+  USB_KEYMAP(0x0c00b8, 0x0000, 0xe02c, 0xffff, "Eject"),
+  USB_KEYMAP(0x0c00cd, 0x0000, 0xe022, 0xffff, "MediaPlayPause"),
+  USB_KEYMAP(0x0c018a, 0x0000, 0xe01e, 0xffff, "LaunchMail"),  // AL_EmailReader
+  USB_KEYMAP(0x0c0192, 0x0094, 0x0000, 0xffff, NULL),  // AL_Calculator
+  // USB#0c0194: My Computer
+  USB_KEYMAP(0x0c0194, 0x00a5, 0x0000, 0xffff, NULL),  // AL_LocalMachineBrowser
+  USB_KEYMAP(0x0c01a7, 0x00f3, 0x0000, 0xffff, NULL),  // AL_Documents
+  // USB#0c01b4: Home Directory
+  USB_KEYMAP(0x0c01b4, 0x0098, 0x0000, 0xffff, NULL),  // AL_FileBrowser (Explorer)
+  USB_KEYMAP(0x0c0221, 0x0000, 0xe065, 0xffff, "BrowserSearch"),   // AC_Search
+  USB_KEYMAP(0x0c0223, 0x0000, 0xe032, 0xffff, "BrowserHome"),     // AC_Home
+  USB_KEYMAP(0x0c0224, 0x00a6, 0xe06a, 0xffff, "BrowserBack"),     // AC_Back
+  USB_KEYMAP(0x0c0225, 0x00a7, 0xe069, 0xffff, "BrowserForward"),  // AC_Forward
+  USB_KEYMAP(0x0c0226, 0x0000, 0xe068, 0xffff, "BrowserStop"),     // AC_Stop
+  USB_KEYMAP(0x0c0227, 0x00b5, 0xe067, 0xffff, "BrowserRefresh"),  // AC_Refresh (Reload)
+  USB_KEYMAP(0x0c022a, 0x00a4, 0xe066, 0xffff, NULL),  // AC_Bookmarks (Favorites)
+  USB_KEYMAP(0x0c0289, 0x00f0, 0x0000, 0xffff, NULL),  // AC_Reply
+  USB_KEYMAP(0x0c028b, 0x00f1, 0x0000, 0xffff, NULL),  // AC_ForwardMsg (MailForward)
+  USB_KEYMAP(0x0c028c, 0x00ef, 0x0000, 0xffff, NULL),  // AC_Send
+};
+
+#endif  // UI_EVENTS_KEYCODES_DOM4_KEYCODE_CONVERTER_DATA_H_
diff --git a/ui/events/keycodes/dom4/keycode_converter_unittest.cc b/ui/events/keycodes/dom4/keycode_converter_unittest.cc
new file mode 100644
index 0000000..0b96583
--- /dev/null
+++ b/ui/events/keycodes/dom4/keycode_converter_unittest.cc
@@ -0,0 +1,112 @@
+// Copyright 2013 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 "ui/events/keycodes/dom4/keycode_converter.h"
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ui::KeycodeConverter;
+
+namespace {
+
+#if defined(OS_WIN)
+const size_t kExpectedMappedKeyCount = 138;
+#elif defined(OS_LINUX)
+const size_t kExpectedMappedKeyCount = 145;
+#elif defined(OS_MACOSX)
+const size_t kExpectedMappedKeyCount = 118;
+#else
+const size_t kExpectedMappedKeyCount = 0;
+#endif
+
+const uint32_t kUsbNonExistentKeycode = 0xffffff;
+const uint32_t kUsbUsBackslash =        0x070031;
+const uint32_t kUsbNonUsHash =          0x070032;
+
+TEST(UsbKeycodeMap, Basic) {
+  // Verify that the first element in the table is the "invalid" code.
+  const ui::KeycodeMapEntry* keycode_map =
+      ui::KeycodeConverter::GetKeycodeMapForTest();
+  EXPECT_EQ(ui::KeycodeConverter::InvalidUsbKeycode(),
+            keycode_map[0].usb_keycode);
+  EXPECT_EQ(ui::KeycodeConverter::InvalidNativeKeycode(),
+            keycode_map[0].native_keycode);
+  EXPECT_STREQ(ui::KeycodeConverter::InvalidKeyboardEventCode(),
+               "Unidentified");
+  EXPECT_EQ(ui::KeycodeConverter::InvalidNativeKeycode(),
+            ui::KeycodeConverter::CodeToNativeKeycode("Unidentified"));
+
+  // Verify that there are no duplicate entries in the mapping.
+  std::map<uint32_t, uint16_t> usb_to_native;
+  std::map<uint16_t, uint32_t> native_to_usb;
+  size_t numEntries = ui::KeycodeConverter::NumKeycodeMapEntriesForTest();
+  for (size_t i = 0; i < numEntries; ++i) {
+    const ui::KeycodeMapEntry* entry = &keycode_map[i];
+    // Don't test keys with no native keycode mapping on this platform.
+    if (entry->native_keycode == ui::KeycodeConverter::InvalidNativeKeycode())
+      continue;
+
+    // Verify UsbKeycodeToNativeKeycode works for this key.
+    EXPECT_EQ(
+        entry->native_keycode,
+        ui::KeycodeConverter::UsbKeycodeToNativeKeycode(entry->usb_keycode));
+
+    // Verify CodeToNativeKeycode and NativeKeycodeToCode work correctly.
+    if (entry->code) {
+      EXPECT_EQ(entry->native_keycode,
+                ui::KeycodeConverter::CodeToNativeKeycode(entry->code));
+      EXPECT_STREQ(
+          entry->code,
+          ui::KeycodeConverter::NativeKeycodeToCode(entry->native_keycode));
+    }
+    else {
+      EXPECT_EQ(ui::KeycodeConverter::InvalidNativeKeycode(),
+                ui::KeycodeConverter::CodeToNativeKeycode(entry->code));
+    }
+
+    // Verify that the USB or native codes aren't duplicated.
+    EXPECT_EQ(0U, usb_to_native.count(entry->usb_keycode))
+        << " duplicate of USB code 0x" << std::hex << std::setfill('0')
+        << std::setw(6) << entry->usb_keycode
+        << " to native 0x"
+        << std::setw(4) << entry->native_keycode
+        << " (previous was 0x"
+        << std::setw(4) << usb_to_native[entry->usb_keycode]
+        << ")";
+    usb_to_native[entry->usb_keycode] = entry->native_keycode;
+    EXPECT_EQ(0U, native_to_usb.count(entry->native_keycode))
+        << " duplicate of native code 0x" << std::hex << std::setfill('0')
+        << std::setw(4) << entry->native_keycode
+        << " to USB 0x"
+        << std::setw(6) << entry->usb_keycode
+        << " (previous was 0x"
+        << std::setw(6) << native_to_usb[entry->native_keycode]
+        << ")";
+    native_to_usb[entry->native_keycode] = entry->usb_keycode;
+  }
+  ASSERT_EQ(usb_to_native.size(), native_to_usb.size());
+
+  // Verify that the number of mapped keys is what we expect, i.e. we haven't
+  // lost any, and if we've added some then the expectation has been updated.
+  EXPECT_EQ(kExpectedMappedKeyCount, usb_to_native.size());
+}
+
+TEST(UsbKeycodeMap, NonExistent) {
+  // Verify that UsbKeycodeToNativeKeycode works for a non-existent USB keycode.
+  EXPECT_EQ(
+      ui::KeycodeConverter::InvalidNativeKeycode(),
+      ui::KeycodeConverter::UsbKeycodeToNativeKeycode(kUsbNonExistentKeycode));
+}
+
+TEST(UsbKeycodeMap, UsBackslashIsNonUsHash) {
+  // Verify that UsbKeycodeToNativeKeycode treats the non-US "hash" key
+  // as equivalent to the US "backslash" key.
+  EXPECT_EQ(ui::KeycodeConverter::UsbKeycodeToNativeKeycode(kUsbUsBackslash),
+            ui::KeycodeConverter::UsbKeycodeToNativeKeycode(kUsbNonUsHash));
+}
+
+}  // namespace
diff --git a/ui/events/keycodes/keyboard_code_conversion.cc b/ui/events/keycodes/keyboard_code_conversion.cc
new file mode 100644
index 0000000..a812b51
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 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 "ui/events/keycodes/keyboard_code_conversion.h"
+
+#include "ui/events/event_constants.h"
+
+namespace ui {
+
+uint16 GetCharacterFromKeyCode(KeyboardCode key_code, int flags) {
+  const bool ctrl = (flags & EF_CONTROL_DOWN) != 0;
+  const bool shift = (flags & EF_SHIFT_DOWN) != 0;
+  const bool upper = shift ^ ((flags & EF_CAPS_LOCK_DOWN) != 0);
+
+  // Following Windows behavior to map ctrl-a ~ ctrl-z to \x01 ~ \x1A.
+  if (key_code >= VKEY_A && key_code <= VKEY_Z)
+    return key_code - VKEY_A + (ctrl ? 1 : (upper ? 'A' : 'a'));
+
+  // Other ctrl characters
+  if (ctrl) {
+    if (shift) {
+      // following graphics chars require shift key to input.
+      switch (key_code) {
+        // ctrl-@ maps to \x00 (Null byte)
+        case VKEY_2:
+          return 0;
+        // ctrl-^ maps to \x1E (Record separator, Information separator two)
+        case VKEY_6:
+          return 0x1E;
+        // ctrl-_ maps to \x1F (Unit separator, Information separator one)
+        case VKEY_OEM_MINUS:
+          return 0x1F;
+        // Returns 0 for all other keys to avoid inputting unexpected chars.
+        default:
+          return 0;
+      }
+    } else {
+      switch (key_code) {
+        // ctrl-[ maps to \x1B (Escape)
+        case VKEY_OEM_4:
+          return 0x1B;
+        // ctrl-\ maps to \x1C (File separator, Information separator four)
+        case VKEY_OEM_5:
+          return 0x1C;
+        // ctrl-] maps to \x1D (Group separator, Information separator three)
+        case VKEY_OEM_6:
+          return 0x1D;
+        // ctrl-Enter maps to \x0A (Line feed)
+        case VKEY_RETURN:
+          return 0x0A;
+        // Returns 0 for all other keys to avoid inputting unexpected chars.
+        default:
+          return 0;
+      }
+    }
+  }
+
+  // For IME support.
+  if (key_code == ui::VKEY_PROCESSKEY)
+    return 0xE5;
+
+  // Normal characters
+  if (key_code >= VKEY_0 && key_code <= VKEY_9) {
+    return shift ? ")!@#$%^&*("[key_code - VKEY_0] :
+        static_cast<uint16>(key_code);
+  } else if (key_code >= VKEY_NUMPAD0 && key_code <= VKEY_NUMPAD9) {
+    return key_code - VKEY_NUMPAD0 + '0';
+  }
+
+  switch (key_code) {
+    case VKEY_TAB:
+      return '\t';
+    case VKEY_RETURN:
+      return '\r';
+    case VKEY_MULTIPLY:
+      return '*';
+    case VKEY_ADD:
+      return '+';
+    case VKEY_SUBTRACT:
+      return '-';
+    case VKEY_DECIMAL:
+      return '.';
+    case VKEY_DIVIDE:
+      return '/';
+    case VKEY_SPACE:
+      return ' ';
+    case VKEY_OEM_1:
+      return shift ? ':' : ';';
+    case VKEY_OEM_PLUS:
+      return shift ? '+' : '=';
+    case VKEY_OEM_COMMA:
+      return shift ? '<' : ',';
+    case VKEY_OEM_MINUS:
+      return shift ? '_' : '-';
+    case VKEY_OEM_PERIOD:
+      return shift ? '>' : '.';
+    case VKEY_OEM_2:
+      return shift ? '?' : '/';
+    case VKEY_OEM_3:
+      return shift ? '~' : '`';
+    case VKEY_OEM_4:
+      return shift ? '{' : '[';
+    case VKEY_OEM_5:
+      return shift ? '|' : '\\';
+    case VKEY_OEM_6:
+      return shift ? '}' : ']';
+    case VKEY_OEM_7:
+      return shift ? '"' : '\'';
+    default:
+      return 0;
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion.h b/ui/events/keycodes/keyboard_code_conversion.h
new file mode 100644
index 0000000..b7bdd57
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011 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 UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace ui {
+
+// A helper function to get the character generated by a key event in a
+// platform independent way. It supports control characters as well.
+// It assumes a US keyboard layout is used, so it may only be used when there
+// is no native event or no better way to get the character.
+// For example, if a virtual keyboard implementation can only generate key
+// events with key_code and flags information, then there is no way for us to
+// determine the actual character that should be generate by the key. Because
+// a key_code only represents a physical key on the keyboard, it has nothing
+// to do with the actual character printed on that key. In such case, the only
+// thing we can do is to assume that we are using a US keyboard and get the
+// character according to US keyboard layout definition.
+// If a virtual keyboard implementation wants to support other keyboard
+// layouts, that may generate different text for a certain key than on a US
+// keyboard, a special native event object should be introduced to carry extra
+// information to help determine the correct character.
+// Take XKeyEvent as an example, it contains not only keycode and modifier
+// flags but also group and other extra XKB information to help determine the
+// correct character. That's why we can use XLookupString() function to get
+// the correct text generated by a X key event (See how is GetCharacter()
+// implemented in event_x.cc).
+EVENTS_BASE_EXPORT uint16 GetCharacterFromKeyCode(KeyboardCode key_code,
+                                                  int flags);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_android.cc b/ui/events/keycodes/keyboard_code_conversion_android.cc
new file mode 100644
index 0000000..b7bdc83
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_android.cc
@@ -0,0 +1,307 @@
+// Copyright 2013 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 "ui/events/keycodes/keyboard_code_conversion_android.h"
+
+#include <android/keycodes.h>
+
+namespace ui {
+
+namespace {
+
+// The Android NDK does not provide values for these yet:
+enum {
+  AKEYCODE_ESCAPE          = 111,
+  AKEYCODE_FORWARD_DEL     = 112,
+  AKEYCODE_CTRL_LEFT       = 113,
+  AKEYCODE_CTRL_RIGHT      = 114,
+  AKEYCODE_CAPS_LOCK       = 115,
+  AKEYCODE_SCROLL_LOCK     = 116,
+  AKEYCODE_META_LEFT       = 117,
+  AKEYCODE_META_RIGHT      = 118,
+  AKEYCODE_BREAK           = 121,
+  AKEYCODE_MOVE_HOME       = 122,
+  AKEYCODE_MOVE_END        = 123,
+  AKEYCODE_INSERT          = 124,
+  AKEYCODE_MEDIA_PLAY      = 126,
+  AKEYCODE_MEDIA_PAUSE     = 127,
+  AKEYCODE_F1              = 131,
+  AKEYCODE_F2              = 132,
+  AKEYCODE_F3              = 133,
+  AKEYCODE_F4              = 134,
+  AKEYCODE_F5              = 135,
+  AKEYCODE_F6              = 136,
+  AKEYCODE_F7              = 137,
+  AKEYCODE_F8              = 138,
+  AKEYCODE_F9              = 139,
+  AKEYCODE_F10             = 140,
+  AKEYCODE_F11             = 141,
+  AKEYCODE_F12             = 142,
+  AKEYCODE_NUM_LOCK        = 143,
+  AKEYCODE_NUMPAD_0        = 144,
+  AKEYCODE_NUMPAD_1        = 145,
+  AKEYCODE_NUMPAD_2        = 146,
+  AKEYCODE_NUMPAD_3        = 147,
+  AKEYCODE_NUMPAD_4        = 148,
+  AKEYCODE_NUMPAD_5        = 149,
+  AKEYCODE_NUMPAD_6        = 150,
+  AKEYCODE_NUMPAD_7        = 151,
+  AKEYCODE_NUMPAD_8        = 152,
+  AKEYCODE_NUMPAD_9        = 153,
+  AKEYCODE_NUMPAD_DIVIDE   = 154,
+  AKEYCODE_NUMPAD_MULTIPLY = 155,
+  AKEYCODE_NUMPAD_SUBTRACT = 156,
+  AKEYCODE_NUMPAD_ADD      = 157,
+  AKEYCODE_NUMPAD_DOT      = 158,
+  AKEYCODE_VOLUME_MUTE     = 164,
+  AKEYCODE_CHANNEL_UP      = 166,
+  AKEYCODE_CHANNEL_DOWN    = 167,
+};
+
+}  // namespace
+
+KeyboardCode KeyboardCodeFromAndroidKeyCode(int keycode) {
+  // Does not provide all key codes, and does not handle all keys.
+  switch (keycode) {
+    case AKEYCODE_DEL:
+      return VKEY_BACK;
+    case AKEYCODE_TAB:
+      return VKEY_TAB;
+    case AKEYCODE_CLEAR:
+      return VKEY_CLEAR;
+    case AKEYCODE_DPAD_CENTER:
+    case AKEYCODE_ENTER:
+      return VKEY_RETURN;
+    case AKEYCODE_SHIFT_LEFT:
+      return VKEY_LSHIFT;
+    case AKEYCODE_SHIFT_RIGHT:
+      return VKEY_RSHIFT;
+    // Back will serve as escape, although we may not have access to it.
+    case AKEYCODE_BACK:
+      return VKEY_ESCAPE;
+    case AKEYCODE_SPACE:
+      return VKEY_SPACE;
+    case AKEYCODE_MOVE_HOME:
+      return VKEY_HOME;
+    case AKEYCODE_DPAD_LEFT:
+      return VKEY_LEFT;
+    case AKEYCODE_DPAD_UP:
+      return VKEY_UP;
+    case AKEYCODE_DPAD_RIGHT:
+      return VKEY_RIGHT;
+    case AKEYCODE_DPAD_DOWN:
+      return VKEY_DOWN;
+    case AKEYCODE_0:
+      return VKEY_0;
+    case AKEYCODE_1:
+      return VKEY_1;
+    case AKEYCODE_2:
+      return VKEY_2;
+    case AKEYCODE_3:
+      return VKEY_3;
+    case AKEYCODE_4:
+      return VKEY_4;
+    case AKEYCODE_5:
+      return VKEY_5;
+    case AKEYCODE_6:
+      return VKEY_6;
+    case AKEYCODE_7:
+      return VKEY_7;
+    case AKEYCODE_8:
+      return VKEY_8;
+    case AKEYCODE_9:
+      return VKEY_9;
+    case AKEYCODE_A:
+      return VKEY_A;
+    case AKEYCODE_B:
+      return VKEY_B;
+    case AKEYCODE_C:
+      return VKEY_C;
+    case AKEYCODE_D:
+      return VKEY_D;
+    case AKEYCODE_E:
+      return VKEY_E;
+    case AKEYCODE_F:
+      return VKEY_F;
+    case AKEYCODE_G:
+      return VKEY_G;
+    case AKEYCODE_H:
+      return VKEY_H;
+    case AKEYCODE_I:
+      return VKEY_I;
+    case AKEYCODE_J:
+      return VKEY_J;
+    case AKEYCODE_K:
+      return VKEY_K;
+    case AKEYCODE_L:
+      return VKEY_L;
+    case AKEYCODE_M:
+      return VKEY_M;
+    case AKEYCODE_N:
+      return VKEY_N;
+    case AKEYCODE_O:
+      return VKEY_O;
+    case AKEYCODE_P:
+      return VKEY_P;
+    case AKEYCODE_Q:
+      return VKEY_Q;
+    case AKEYCODE_R:
+      return VKEY_R;
+    case AKEYCODE_S:
+      return VKEY_S;
+    case AKEYCODE_T:
+      return VKEY_T;
+    case AKEYCODE_U:
+      return VKEY_U;
+    case AKEYCODE_V:
+      return VKEY_V;
+    case AKEYCODE_W:
+      return VKEY_W;
+    case AKEYCODE_X:
+      return VKEY_X;
+    case AKEYCODE_Y:
+      return VKEY_Y;
+    case AKEYCODE_Z:
+      return VKEY_Z;
+    case AKEYCODE_VOLUME_DOWN:
+      return VKEY_VOLUME_DOWN;
+    case AKEYCODE_VOLUME_UP:
+      return VKEY_VOLUME_UP;
+    case AKEYCODE_MEDIA_NEXT:
+      return VKEY_MEDIA_NEXT_TRACK;
+    case AKEYCODE_MEDIA_PREVIOUS:
+      return VKEY_MEDIA_PREV_TRACK;
+    case AKEYCODE_MEDIA_STOP:
+      return VKEY_MEDIA_STOP;
+    case AKEYCODE_MEDIA_PAUSE:
+      return VKEY_MEDIA_PLAY_PAUSE;
+    // Colon key.
+    case AKEYCODE_SEMICOLON:
+      return VKEY_OEM_1;
+    case AKEYCODE_COMMA:
+      return VKEY_OEM_COMMA;
+    case AKEYCODE_MINUS:
+      return VKEY_OEM_MINUS;
+    case AKEYCODE_EQUALS:
+      return VKEY_OEM_PLUS;
+    case AKEYCODE_PERIOD:
+      return VKEY_OEM_PERIOD;
+    case AKEYCODE_SLASH:
+      return VKEY_OEM_2;
+    case AKEYCODE_LEFT_BRACKET:
+      return VKEY_OEM_4;
+    case AKEYCODE_BACKSLASH:
+      return VKEY_OEM_5;
+    case AKEYCODE_RIGHT_BRACKET:
+      return VKEY_OEM_6;
+    case AKEYCODE_MUTE:
+    case AKEYCODE_VOLUME_MUTE:
+      return VKEY_VOLUME_MUTE;
+    case AKEYCODE_ESCAPE:
+      return VKEY_ESCAPE;
+    case AKEYCODE_MEDIA_PLAY:
+    case AKEYCODE_MEDIA_PLAY_PAUSE:
+      return VKEY_MEDIA_PLAY_PAUSE;
+    case AKEYCODE_MOVE_END:
+      return VKEY_END;
+    case AKEYCODE_ALT_LEFT:
+      return VKEY_LMENU;
+    case AKEYCODE_ALT_RIGHT:
+      return VKEY_RMENU;
+    case AKEYCODE_GRAVE:
+      return VKEY_OEM_3;
+    case AKEYCODE_APOSTROPHE:
+      return VKEY_OEM_3;
+    case AKEYCODE_MEDIA_REWIND:
+      return VKEY_OEM_103;
+    case AKEYCODE_MEDIA_FAST_FORWARD:
+      return VKEY_OEM_104;
+    case AKEYCODE_PAGE_UP:
+      return VKEY_PRIOR;
+    case AKEYCODE_PAGE_DOWN:
+      return VKEY_NEXT;
+    case AKEYCODE_FORWARD_DEL:
+      return VKEY_DELETE;
+    case AKEYCODE_CTRL_LEFT:
+      return VKEY_LCONTROL;
+    case AKEYCODE_CTRL_RIGHT:
+      return VKEY_RCONTROL;
+    case AKEYCODE_CAPS_LOCK:
+      return VKEY_CAPITAL;
+    case AKEYCODE_SCROLL_LOCK:
+      return VKEY_SCROLL;
+    case AKEYCODE_META_LEFT:
+      return VKEY_LWIN;
+    case AKEYCODE_META_RIGHT:
+      return VKEY_RWIN;
+    case AKEYCODE_BREAK:
+      return VKEY_PAUSE;
+    case AKEYCODE_INSERT:
+      return VKEY_INSERT;
+    case AKEYCODE_F1:
+      return VKEY_F1;
+    case AKEYCODE_F2:
+      return VKEY_F2;
+    case AKEYCODE_F3:
+      return VKEY_F3;
+    case AKEYCODE_F4:
+      return VKEY_F4;
+    case AKEYCODE_F5:
+      return VKEY_F5;
+    case AKEYCODE_F6:
+      return VKEY_F6;
+    case AKEYCODE_F7:
+      return VKEY_F7;
+    case AKEYCODE_F8:
+      return VKEY_F8;
+    case AKEYCODE_F9:
+      return VKEY_F9;
+    case AKEYCODE_F10:
+      return VKEY_F10;
+    case AKEYCODE_F11:
+      return VKEY_F11;
+    case AKEYCODE_F12:
+      return VKEY_F12;
+    case AKEYCODE_NUM_LOCK:
+      return VKEY_NUMLOCK;
+    case AKEYCODE_NUMPAD_0:
+      return VKEY_NUMPAD0;
+    case AKEYCODE_NUMPAD_1:
+      return VKEY_NUMPAD1;
+    case AKEYCODE_NUMPAD_2:
+      return VKEY_NUMPAD2;
+    case AKEYCODE_NUMPAD_3:
+      return VKEY_NUMPAD3;
+    case AKEYCODE_NUMPAD_4:
+      return VKEY_NUMPAD4;
+    case AKEYCODE_NUMPAD_5:
+      return VKEY_NUMPAD5;
+    case AKEYCODE_NUMPAD_6:
+      return VKEY_NUMPAD6;
+    case AKEYCODE_NUMPAD_7:
+      return VKEY_NUMPAD7;
+    case AKEYCODE_NUMPAD_8:
+      return VKEY_NUMPAD8;
+    case AKEYCODE_NUMPAD_9:
+      return VKEY_NUMPAD9;
+    case AKEYCODE_NUMPAD_DIVIDE:
+      return VKEY_DIVIDE;
+    case AKEYCODE_NUMPAD_MULTIPLY:
+      return VKEY_MULTIPLY;
+    case AKEYCODE_NUMPAD_SUBTRACT:
+      return VKEY_SUBTRACT;
+    case AKEYCODE_NUMPAD_ADD:
+      return VKEY_ADD;
+    case AKEYCODE_NUMPAD_DOT:
+      return VKEY_DECIMAL;
+    case AKEYCODE_CHANNEL_UP:
+      return VKEY_PRIOR;
+    case AKEYCODE_CHANNEL_DOWN:
+      return VKEY_NEXT;
+    default:
+      return VKEY_UNKNOWN;
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_android.h b/ui/events/keycodes/keyboard_code_conversion_android.h
new file mode 100644
index 0000000..23552f5
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_android.h
@@ -0,0 +1,17 @@
+// Copyright 2013 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 UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_ANDROID_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_ANDROID_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace ui {
+
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromAndroidKeyCode(int keycode);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_ANDROID_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_mac.h b/ui/events/keycodes/keyboard_code_conversion_mac.h
new file mode 100644
index 0000000..fee3981
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_mac.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2011 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 UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_MAC_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_MAC_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace ui {
+
+// We use windows virtual keycodes throughout our keyboard event related code,
+// including unit tests. But Mac uses a different set of virtual keycodes.
+// This function converts a windows virtual keycode into Mac's virtual key code
+// and corresponding unicode character. |flags| is the Cocoa modifiers mask
+// such as NSControlKeyMask, NSShiftKeyMask, etc.
+// When success, the corresponding Mac's virtual key code will be returned.
+// The corresponding unicode character will be stored in |character|, and the
+// corresponding unicode character ignoring the modifiers will be stored in
+// |characterIgnoringModifiers|.
+// -1 will be returned if the keycode can't be converted.
+// This function is mainly for simulating keyboard events in unit tests.
+// See |KeyboardCodeFromNSEvent| for reverse conversion.
+EVENTS_BASE_EXPORT int MacKeyCodeForWindowsKeyCode(
+    KeyboardCode keycode,
+    NSUInteger flags,
+    unichar* character,
+    unichar* characterIgnoringModifiers);
+
+// This implementation cribbed from:
+//   third_party/WebKit/Source/web/mac/WebInputEventFactory.mm
+// Converts |event| into a |KeyboardCode|.  The mapping is not direct as the Mac
+// has a different notion of key codes.
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromNSEvent(NSEvent* event);
+
+EVENTS_BASE_EXPORT const char* CodeFromNSEvent(NSEvent* event);
+
+} // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_MAC_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_mac.mm b/ui/events/keycodes/keyboard_code_conversion_mac.mm
new file mode 100644
index 0000000..8f7daae
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_mac.mm
@@ -0,0 +1,551 @@
+// Copyright (c) 2012 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.
+
+#import "ui/events/keycodes/keyboard_code_conversion_mac.h"
+
+#include <algorithm>
+
+#import <Carbon/Carbon.h>
+
+#include "base/logging.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+
+namespace ui {
+
+namespace {
+
+// A struct to hold a Windows keycode to Mac virtual keycode mapping.
+struct KeyCodeMap {
+  KeyboardCode keycode;
+  int macKeycode;
+  unichar characterIgnoringModifiers;
+};
+
+// Customized less operator for using std::lower_bound() on a KeyCodeMap array.
+bool operator<(const KeyCodeMap& a, const KeyCodeMap& b) {
+  return a.keycode < b.keycode;
+}
+
+// This array must keep sorted ascending according to the value of |keycode|,
+// so that we can binary search it.
+// TODO(suzhe): This map is not complete, missing entries have macKeycode == -1.
+const KeyCodeMap kKeyCodesMap[] = {
+  { VKEY_BACK /* 0x08 */, kVK_Delete, kBackspaceCharCode },
+  { VKEY_TAB /* 0x09 */, kVK_Tab, kTabCharCode },
+  { VKEY_BACKTAB /* 0x0A */, 0x21E4, '\031' },
+  { VKEY_CLEAR /* 0x0C */, kVK_ANSI_KeypadClear, kClearCharCode },
+  { VKEY_RETURN /* 0x0D */, kVK_Return, kReturnCharCode },
+  { VKEY_SHIFT /* 0x10 */, kVK_Shift, 0 },
+  { VKEY_CONTROL /* 0x11 */, kVK_Control, 0 },
+  { VKEY_MENU /* 0x12 */, kVK_Option, 0 },
+  { VKEY_PAUSE /* 0x13 */, -1, NSPauseFunctionKey },
+  { VKEY_CAPITAL /* 0x14 */, kVK_CapsLock, 0 },
+  { VKEY_KANA /* 0x15 */, kVK_JIS_Kana, 0 },
+  { VKEY_HANGUL /* 0x15 */, -1, 0 },
+  { VKEY_JUNJA /* 0x17 */, -1, 0 },
+  { VKEY_FINAL /* 0x18 */, -1, 0 },
+  { VKEY_HANJA /* 0x19 */, -1, 0 },
+  { VKEY_KANJI /* 0x19 */, -1, 0 },
+  { VKEY_ESCAPE /* 0x1B */, kVK_Escape, kEscapeCharCode },
+  { VKEY_CONVERT /* 0x1C */, -1, 0 },
+  { VKEY_NONCONVERT /* 0x1D */, -1, 0 },
+  { VKEY_ACCEPT /* 0x1E */, -1, 0 },
+  { VKEY_MODECHANGE /* 0x1F */, -1, 0 },
+  { VKEY_SPACE /* 0x20 */, kVK_Space, kSpaceCharCode },
+  { VKEY_PRIOR /* 0x21 */, kVK_PageUp, NSPageUpFunctionKey },
+  { VKEY_NEXT /* 0x22 */, kVK_PageDown, NSPageDownFunctionKey },
+  { VKEY_END /* 0x23 */, kVK_End, NSEndFunctionKey },
+  { VKEY_HOME /* 0x24 */, kVK_Home, NSHomeFunctionKey },
+  { VKEY_LEFT /* 0x25 */, kVK_LeftArrow, NSLeftArrowFunctionKey },
+  { VKEY_UP /* 0x26 */, kVK_UpArrow, NSUpArrowFunctionKey },
+  { VKEY_RIGHT /* 0x27 */, kVK_RightArrow, NSRightArrowFunctionKey },
+  { VKEY_DOWN /* 0x28 */, kVK_DownArrow, NSDownArrowFunctionKey },
+  { VKEY_SELECT /* 0x29 */, -1, 0 },
+  { VKEY_PRINT /* 0x2A */, -1, NSPrintFunctionKey },
+  { VKEY_EXECUTE /* 0x2B */, -1, NSExecuteFunctionKey },
+  { VKEY_SNAPSHOT /* 0x2C */, -1, NSPrintScreenFunctionKey },
+  { VKEY_INSERT /* 0x2D */, -1, NSInsertFunctionKey },
+  { VKEY_DELETE /* 0x2E */, kVK_ForwardDelete, kDeleteCharCode },
+  { VKEY_HELP /* 0x2F */, kVK_Help, kHelpCharCode },
+  { VKEY_0 /* 0x30 */, kVK_ANSI_0, '0' },
+  { VKEY_1 /* 0x31 */, kVK_ANSI_1, '1' },
+  { VKEY_2 /* 0x32 */, kVK_ANSI_2, '2' },
+  { VKEY_3 /* 0x33 */, kVK_ANSI_3, '3' },
+  { VKEY_4 /* 0x34 */, kVK_ANSI_4, '4' },
+  { VKEY_5 /* 0x35 */, kVK_ANSI_5, '5' },
+  { VKEY_6 /* 0x36 */, kVK_ANSI_6, '6' },
+  { VKEY_7 /* 0x37 */, kVK_ANSI_7, '7' },
+  { VKEY_8 /* 0x38 */, kVK_ANSI_8, '8' },
+  { VKEY_9 /* 0x39 */, kVK_ANSI_9, '9' },
+  { VKEY_A /* 0x41 */, kVK_ANSI_A, 'a' },
+  { VKEY_B /* 0x42 */, kVK_ANSI_B, 'b' },
+  { VKEY_C /* 0x43 */, kVK_ANSI_C, 'c' },
+  { VKEY_D /* 0x44 */, kVK_ANSI_D, 'd' },
+  { VKEY_E /* 0x45 */, kVK_ANSI_E, 'e' },
+  { VKEY_F /* 0x46 */, kVK_ANSI_F, 'f' },
+  { VKEY_G /* 0x47 */, kVK_ANSI_G, 'g' },
+  { VKEY_H /* 0x48 */, kVK_ANSI_H, 'h' },
+  { VKEY_I /* 0x49 */, kVK_ANSI_I, 'i' },
+  { VKEY_J /* 0x4A */, kVK_ANSI_J, 'j' },
+  { VKEY_K /* 0x4B */, kVK_ANSI_K, 'k' },
+  { VKEY_L /* 0x4C */, kVK_ANSI_L, 'l' },
+  { VKEY_M /* 0x4D */, kVK_ANSI_M, 'm' },
+  { VKEY_N /* 0x4E */, kVK_ANSI_N, 'n' },
+  { VKEY_O /* 0x4F */, kVK_ANSI_O, 'o' },
+  { VKEY_P /* 0x50 */, kVK_ANSI_P, 'p' },
+  { VKEY_Q /* 0x51 */, kVK_ANSI_Q, 'q' },
+  { VKEY_R /* 0x52 */, kVK_ANSI_R, 'r' },
+  { VKEY_S /* 0x53 */, kVK_ANSI_S, 's' },
+  { VKEY_T /* 0x54 */, kVK_ANSI_T, 't' },
+  { VKEY_U /* 0x55 */, kVK_ANSI_U, 'u' },
+  { VKEY_V /* 0x56 */, kVK_ANSI_V, 'v' },
+  { VKEY_W /* 0x57 */, kVK_ANSI_W, 'w' },
+  { VKEY_X /* 0x58 */, kVK_ANSI_X, 'x' },
+  { VKEY_Y /* 0x59 */, kVK_ANSI_Y, 'y' },
+  { VKEY_Z /* 0x5A */, kVK_ANSI_Z, 'z' },
+  { VKEY_LWIN /* 0x5B */, kVK_Command, 0 },
+  { VKEY_RWIN /* 0x5C */, 0x36, 0 },
+  { VKEY_APPS /* 0x5D */, 0x36, 0 },
+  { VKEY_SLEEP /* 0x5F */, -1, 0 },
+  { VKEY_NUMPAD0 /* 0x60 */, kVK_ANSI_Keypad0, '0' },
+  { VKEY_NUMPAD1 /* 0x61 */, kVK_ANSI_Keypad1, '1' },
+  { VKEY_NUMPAD2 /* 0x62 */, kVK_ANSI_Keypad2, '2' },
+  { VKEY_NUMPAD3 /* 0x63 */, kVK_ANSI_Keypad3, '3' },
+  { VKEY_NUMPAD4 /* 0x64 */, kVK_ANSI_Keypad4, '4' },
+  { VKEY_NUMPAD5 /* 0x65 */, kVK_ANSI_Keypad5, '5' },
+  { VKEY_NUMPAD6 /* 0x66 */, kVK_ANSI_Keypad6, '6' },
+  { VKEY_NUMPAD7 /* 0x67 */, kVK_ANSI_Keypad7, '7' },
+  { VKEY_NUMPAD8 /* 0x68 */, kVK_ANSI_Keypad8, '8' },
+  { VKEY_NUMPAD9 /* 0x69 */, kVK_ANSI_Keypad9, '9' },
+  { VKEY_MULTIPLY /* 0x6A */, kVK_ANSI_KeypadMultiply, '*' },
+  { VKEY_ADD /* 0x6B */, kVK_ANSI_KeypadPlus, '+' },
+  { VKEY_SEPARATOR /* 0x6C */, -1, 0 },
+  { VKEY_SUBTRACT /* 0x6D */, kVK_ANSI_KeypadMinus, '-' },
+  { VKEY_DECIMAL /* 0x6E */, kVK_ANSI_KeypadDecimal, '.' },
+  { VKEY_DIVIDE /* 0x6F */, kVK_ANSI_KeypadDivide, '/' },
+  { VKEY_F1 /* 0x70 */, kVK_F1, NSF1FunctionKey },
+  { VKEY_F2 /* 0x71 */, kVK_F2, NSF2FunctionKey },
+  { VKEY_F3 /* 0x72 */, kVK_F3, NSF3FunctionKey },
+  { VKEY_F4 /* 0x73 */, kVK_F4, NSF4FunctionKey },
+  { VKEY_F5 /* 0x74 */, kVK_F5, NSF5FunctionKey },
+  { VKEY_F6 /* 0x75 */, kVK_F6, NSF6FunctionKey },
+  { VKEY_F7 /* 0x76 */, kVK_F7, NSF7FunctionKey },
+  { VKEY_F8 /* 0x77 */, kVK_F8, NSF8FunctionKey },
+  { VKEY_F9 /* 0x78 */, kVK_F9, NSF9FunctionKey },
+  { VKEY_F10 /* 0x79 */, kVK_F10, NSF10FunctionKey },
+  { VKEY_F11 /* 0x7A */, kVK_F11, NSF11FunctionKey },
+  { VKEY_F12 /* 0x7B */, kVK_F12, NSF12FunctionKey },
+  { VKEY_F13 /* 0x7C */, kVK_F13, NSF13FunctionKey },
+  { VKEY_F14 /* 0x7D */, kVK_F14, NSF14FunctionKey },
+  { VKEY_F15 /* 0x7E */, kVK_F15, NSF15FunctionKey },
+  { VKEY_F16 /* 0x7F */, kVK_F16, NSF16FunctionKey },
+  { VKEY_F17 /* 0x80 */, kVK_F17, NSF17FunctionKey },
+  { VKEY_F18 /* 0x81 */, kVK_F18, NSF18FunctionKey },
+  { VKEY_F19 /* 0x82 */, kVK_F19, NSF19FunctionKey },
+  { VKEY_F20 /* 0x83 */, kVK_F20, NSF20FunctionKey },
+  { VKEY_F21 /* 0x84 */, -1, NSF21FunctionKey },
+  { VKEY_F22 /* 0x85 */, -1, NSF22FunctionKey },
+  { VKEY_F23 /* 0x86 */, -1, NSF23FunctionKey },
+  { VKEY_F24 /* 0x87 */, -1, NSF24FunctionKey },
+  { VKEY_NUMLOCK /* 0x90 */, -1, 0 },
+  { VKEY_SCROLL /* 0x91 */, -1, NSScrollLockFunctionKey },
+  { VKEY_LSHIFT /* 0xA0 */, kVK_Shift, 0 },
+  { VKEY_RSHIFT /* 0xA1 */, kVK_Shift, 0 },
+  { VKEY_LCONTROL /* 0xA2 */, kVK_Control, 0 },
+  { VKEY_RCONTROL /* 0xA3 */, kVK_Control, 0 },
+  { VKEY_LMENU /* 0xA4 */, -1, 0 },
+  { VKEY_RMENU /* 0xA5 */, -1, 0 },
+  { VKEY_BROWSER_BACK /* 0xA6 */, -1, 0 },
+  { VKEY_BROWSER_FORWARD /* 0xA7 */, -1, 0 },
+  { VKEY_BROWSER_REFRESH /* 0xA8 */, -1, 0 },
+  { VKEY_BROWSER_STOP /* 0xA9 */, -1, 0 },
+  { VKEY_BROWSER_SEARCH /* 0xAA */, -1, 0 },
+  { VKEY_BROWSER_FAVORITES /* 0xAB */, -1, 0 },
+  { VKEY_BROWSER_HOME /* 0xAC */, -1, 0 },
+  { VKEY_VOLUME_MUTE /* 0xAD */, -1, 0 },
+  { VKEY_VOLUME_DOWN /* 0xAE */, -1, 0 },
+  { VKEY_VOLUME_UP /* 0xAF */, -1, 0 },
+  { VKEY_MEDIA_NEXT_TRACK /* 0xB0 */, -1, 0 },
+  { VKEY_MEDIA_PREV_TRACK /* 0xB1 */, -1, 0 },
+  { VKEY_MEDIA_STOP /* 0xB2 */, -1, 0 },
+  { VKEY_MEDIA_PLAY_PAUSE /* 0xB3 */, -1, 0 },
+  { VKEY_MEDIA_LAUNCH_MAIL /* 0xB4 */, -1, 0 },
+  { VKEY_MEDIA_LAUNCH_MEDIA_SELECT /* 0xB5 */, -1, 0 },
+  { VKEY_MEDIA_LAUNCH_APP1 /* 0xB6 */, -1, 0 },
+  { VKEY_MEDIA_LAUNCH_APP2 /* 0xB7 */, -1, 0 },
+  { VKEY_OEM_1 /* 0xBA */, kVK_ANSI_Semicolon, ';' },
+  { VKEY_OEM_PLUS /* 0xBB */, kVK_ANSI_Equal, '=' },
+  { VKEY_OEM_COMMA /* 0xBC */, kVK_ANSI_Comma, ',' },
+  { VKEY_OEM_MINUS /* 0xBD */, kVK_ANSI_Minus, '-' },
+  { VKEY_OEM_PERIOD /* 0xBE */, kVK_ANSI_Period, '.' },
+  { VKEY_OEM_2 /* 0xBF */, kVK_ANSI_Slash, '/' },
+  { VKEY_OEM_3 /* 0xC0 */, kVK_ANSI_Grave, '`' },
+  { VKEY_OEM_4 /* 0xDB */, kVK_ANSI_LeftBracket, '[' },
+  { VKEY_OEM_5 /* 0xDC */, kVK_ANSI_Backslash, '\\' },
+  { VKEY_OEM_6 /* 0xDD */, kVK_ANSI_RightBracket, ']' },
+  { VKEY_OEM_7 /* 0xDE */, kVK_ANSI_Quote, '\'' },
+  { VKEY_OEM_8 /* 0xDF */, -1, 0 },
+  { VKEY_OEM_102 /* 0xE2 */, -1, 0 },
+  { VKEY_PROCESSKEY /* 0xE5 */, -1, 0 },
+  { VKEY_PACKET /* 0xE7 */, -1, 0 },
+  { VKEY_ATTN /* 0xF6 */, -1, 0 },
+  { VKEY_CRSEL /* 0xF7 */, -1, 0 },
+  { VKEY_EXSEL /* 0xF8 */, -1, 0 },
+  { VKEY_EREOF /* 0xF9 */, -1, 0 },
+  { VKEY_PLAY /* 0xFA */, -1, 0 },
+  { VKEY_ZOOM /* 0xFB */, -1, 0 },
+  { VKEY_NONAME /* 0xFC */, -1, 0 },
+  { VKEY_PA1 /* 0xFD */, -1, 0 },
+  { VKEY_OEM_CLEAR /* 0xFE */, kVK_ANSI_KeypadClear, kClearCharCode }
+};
+
+// A convenient array for getting symbol characters on the number keys.
+const char kShiftCharsForNumberKeys[] = ")!@#$%^&*(";
+
+// Translates from character code to keyboard code.
+KeyboardCode KeyboardCodeFromCharCode(unichar charCode) {
+  switch (charCode) {
+    case 8: case 0x7F: return VKEY_BACK;
+    case 9: return VKEY_TAB;
+    case 0xD: case 3: return VKEY_RETURN;
+    case 0x1B: return VKEY_ESCAPE;
+    case ' ': return VKEY_SPACE;
+    case NSHomeFunctionKey: return VKEY_HOME;
+    case NSEndFunctionKey: return VKEY_END;
+    case NSPageUpFunctionKey: return VKEY_PRIOR;
+    case NSPageDownFunctionKey: return VKEY_NEXT;
+    case NSUpArrowFunctionKey: return VKEY_UP;
+    case NSDownArrowFunctionKey: return VKEY_DOWN;
+    case NSLeftArrowFunctionKey: return VKEY_LEFT;
+    case NSRightArrowFunctionKey: return VKEY_RIGHT;
+    case NSDeleteFunctionKey: return VKEY_DELETE;
+
+    case '0': case ')': return VKEY_0;
+    case '1': case '!': return VKEY_1;
+    case '2': case '@': return VKEY_2;
+    case '3': case '#': return VKEY_3;
+    case '4': case '$': return VKEY_4;
+    case '5': case '%': return VKEY_5;
+    case '6': case '^': return VKEY_6;
+    case '7': case '&': return VKEY_7;
+    case '8': case '*': return VKEY_8;
+    case '9': case '(': return VKEY_9;
+
+    case 'a': case 'A': return VKEY_A;
+    case 'b': case 'B': return VKEY_B;
+    case 'c': case 'C': return VKEY_C;
+    case 'd': case 'D': return VKEY_D;
+    case 'e': case 'E': return VKEY_E;
+    case 'f': case 'F': return VKEY_F;
+    case 'g': case 'G': return VKEY_G;
+    case 'h': case 'H': return VKEY_H;
+    case 'i': case 'I': return VKEY_I;
+    case 'j': case 'J': return VKEY_J;
+    case 'k': case 'K': return VKEY_K;
+    case 'l': case 'L': return VKEY_L;
+    case 'm': case 'M': return VKEY_M;
+    case 'n': case 'N': return VKEY_N;
+    case 'o': case 'O': return VKEY_O;
+    case 'p': case 'P': return VKEY_P;
+    case 'q': case 'Q': return VKEY_Q;
+    case 'r': case 'R': return VKEY_R;
+    case 's': case 'S': return VKEY_S;
+    case 't': case 'T': return VKEY_T;
+    case 'u': case 'U': return VKEY_U;
+    case 'v': case 'V': return VKEY_V;
+    case 'w': case 'W': return VKEY_W;
+    case 'x': case 'X': return VKEY_X;
+    case 'y': case 'Y': return VKEY_Y;
+    case 'z': case 'Z': return VKEY_Z;
+
+    case NSPauseFunctionKey: return VKEY_PAUSE;
+    case NSSelectFunctionKey: return VKEY_SELECT;
+    case NSPrintFunctionKey: return VKEY_PRINT;
+    case NSExecuteFunctionKey: return VKEY_EXECUTE;
+    case NSPrintScreenFunctionKey: return VKEY_SNAPSHOT;
+    case NSInsertFunctionKey: return VKEY_INSERT;
+    case NSHelpFunctionKey: return VKEY_INSERT;
+
+    case NSF1FunctionKey: return VKEY_F1;
+    case NSF2FunctionKey: return VKEY_F2;
+    case NSF3FunctionKey: return VKEY_F3;
+    case NSF4FunctionKey: return VKEY_F4;
+    case NSF5FunctionKey: return VKEY_F5;
+    case NSF6FunctionKey: return VKEY_F6;
+    case NSF7FunctionKey: return VKEY_F7;
+    case NSF8FunctionKey: return VKEY_F8;
+    case NSF9FunctionKey: return VKEY_F9;
+    case NSF10FunctionKey: return VKEY_F10;
+    case NSF11FunctionKey: return VKEY_F11;
+    case NSF12FunctionKey: return VKEY_F12;
+    case NSF13FunctionKey: return VKEY_F13;
+    case NSF14FunctionKey: return VKEY_F14;
+    case NSF15FunctionKey: return VKEY_F15;
+    case NSF16FunctionKey: return VKEY_F16;
+    case NSF17FunctionKey: return VKEY_F17;
+    case NSF18FunctionKey: return VKEY_F18;
+    case NSF19FunctionKey: return VKEY_F19;
+    case NSF20FunctionKey: return VKEY_F20;
+
+    case NSF21FunctionKey: return VKEY_F21;
+    case NSF22FunctionKey: return VKEY_F22;
+    case NSF23FunctionKey: return VKEY_F23;
+    case NSF24FunctionKey: return VKEY_F24;
+    case NSScrollLockFunctionKey: return VKEY_SCROLL;
+
+      // U.S. Specific mappings.  Mileage may vary.
+    case ';': case ':': return VKEY_OEM_1;
+    case '=': case '+': return VKEY_OEM_PLUS;
+    case ',': case '<': return VKEY_OEM_COMMA;
+    case '-': case '_': return VKEY_OEM_MINUS;
+    case '.': case '>': return VKEY_OEM_PERIOD;
+    case '/': case '?': return VKEY_OEM_2;
+    case '`': case '~': return VKEY_OEM_3;
+    case '[': case '{': return VKEY_OEM_4;
+    case '\\': case '|': return VKEY_OEM_5;
+    case ']': case '}': return VKEY_OEM_6;
+    case '\'': case '"': return VKEY_OEM_7;
+  }
+
+  return VKEY_UNKNOWN;
+}
+
+KeyboardCode KeyboardCodeFromKeyCode(unsigned short keyCode) {
+  static const KeyboardCode kKeyboardCodes[] = {
+    /* 0 */ VKEY_A,
+    /* 1 */ VKEY_S,
+    /* 2 */ VKEY_D,
+    /* 3 */ VKEY_F,
+    /* 4 */ VKEY_H,
+    /* 5 */ VKEY_G,
+    /* 6 */ VKEY_Z,
+    /* 7 */ VKEY_X,
+    /* 8 */ VKEY_C,
+    /* 9 */ VKEY_V,
+    /* 0x0A */ VKEY_OEM_3, // Section key.
+    /* 0x0B */ VKEY_B,
+    /* 0x0C */ VKEY_Q,
+    /* 0x0D */ VKEY_W,
+    /* 0x0E */ VKEY_E,
+    /* 0x0F */ VKEY_R,
+    /* 0x10 */ VKEY_Y,
+    /* 0x11 */ VKEY_T,
+    /* 0x12 */ VKEY_1,
+    /* 0x13 */ VKEY_2,
+    /* 0x14 */ VKEY_3,
+    /* 0x15 */ VKEY_4,
+    /* 0x16 */ VKEY_6,
+    /* 0x17 */ VKEY_5,
+    /* 0x18 */ VKEY_OEM_PLUS, // =+
+    /* 0x19 */ VKEY_9,
+    /* 0x1A */ VKEY_7,
+    /* 0x1B */ VKEY_OEM_MINUS, // -_
+    /* 0x1C */ VKEY_8,
+    /* 0x1D */ VKEY_0,
+    /* 0x1E */ VKEY_OEM_6, // ]}
+    /* 0x1F */ VKEY_O,
+    /* 0x20 */ VKEY_U,
+    /* 0x21 */ VKEY_OEM_4, // {[
+    /* 0x22 */ VKEY_I,
+    /* 0x23 */ VKEY_P,
+    /* 0x24 */ VKEY_RETURN, // Return
+    /* 0x25 */ VKEY_L,
+    /* 0x26 */ VKEY_J,
+    /* 0x27 */ VKEY_OEM_7, // '"
+    /* 0x28 */ VKEY_K,
+    /* 0x29 */ VKEY_OEM_1, // ;:
+    /* 0x2A */ VKEY_OEM_5, // \|
+    /* 0x2B */ VKEY_OEM_COMMA, // ,<
+    /* 0x2C */ VKEY_OEM_2, // /?
+    /* 0x2D */ VKEY_N,
+    /* 0x2E */ VKEY_M,
+    /* 0x2F */ VKEY_OEM_PERIOD, // .>
+    /* 0x30 */ VKEY_TAB,
+    /* 0x31 */ VKEY_SPACE,
+    /* 0x32 */ VKEY_OEM_3, // `~
+    /* 0x33 */ VKEY_BACK, // Backspace
+    /* 0x34 */ VKEY_UNKNOWN, // n/a
+    /* 0x35 */ VKEY_ESCAPE,
+    /* 0x36 */ VKEY_APPS, // Right Command
+    /* 0x37 */ VKEY_LWIN, // Left Command
+    /* 0x38 */ VKEY_SHIFT, // Left Shift
+    /* 0x39 */ VKEY_CAPITAL, // Caps Lock
+    /* 0x3A */ VKEY_MENU, // Left Option
+    /* 0x3B */ VKEY_CONTROL, // Left Ctrl
+    /* 0x3C */ VKEY_SHIFT, // Right Shift
+    /* 0x3D */ VKEY_MENU, // Right Option
+    /* 0x3E */ VKEY_CONTROL, // Right Ctrl
+    /* 0x3F */ VKEY_UNKNOWN, // fn
+    /* 0x40 */ VKEY_F17,
+    /* 0x41 */ VKEY_DECIMAL, // Num Pad .
+    /* 0x42 */ VKEY_UNKNOWN, // n/a
+    /* 0x43 */ VKEY_MULTIPLY, // Num Pad *
+    /* 0x44 */ VKEY_UNKNOWN, // n/a
+    /* 0x45 */ VKEY_ADD, // Num Pad +
+    /* 0x46 */ VKEY_UNKNOWN, // n/a
+    /* 0x47 */ VKEY_CLEAR, // Num Pad Clear
+    /* 0x48 */ VKEY_VOLUME_UP,
+    /* 0x49 */ VKEY_VOLUME_DOWN,
+    /* 0x4A */ VKEY_VOLUME_MUTE,
+    /* 0x4B */ VKEY_DIVIDE, // Num Pad /
+    /* 0x4C */ VKEY_RETURN, // Num Pad Enter
+    /* 0x4D */ VKEY_UNKNOWN, // n/a
+    /* 0x4E */ VKEY_SUBTRACT, // Num Pad -
+    /* 0x4F */ VKEY_F18,
+    /* 0x50 */ VKEY_F19,
+    /* 0x51 */ VKEY_OEM_PLUS, // Num Pad =.
+    /* 0x52 */ VKEY_NUMPAD0,
+    /* 0x53 */ VKEY_NUMPAD1,
+    /* 0x54 */ VKEY_NUMPAD2,
+    /* 0x55 */ VKEY_NUMPAD3,
+    /* 0x56 */ VKEY_NUMPAD4,
+    /* 0x57 */ VKEY_NUMPAD5,
+    /* 0x58 */ VKEY_NUMPAD6,
+    /* 0x59 */ VKEY_NUMPAD7,
+    /* 0x5A */ VKEY_F20,
+    /* 0x5B */ VKEY_NUMPAD8,
+    /* 0x5C */ VKEY_NUMPAD9,
+    /* 0x5D */ VKEY_UNKNOWN, // Yen (JIS Keyboard Only)
+    /* 0x5E */ VKEY_UNKNOWN, // Underscore (JIS Keyboard Only)
+    /* 0x5F */ VKEY_UNKNOWN, // KeypadComma (JIS Keyboard Only)
+    /* 0x60 */ VKEY_F5,
+    /* 0x61 */ VKEY_F6,
+    /* 0x62 */ VKEY_F7,
+    /* 0x63 */ VKEY_F3,
+    /* 0x64 */ VKEY_F8,
+    /* 0x65 */ VKEY_F9,
+    /* 0x66 */ VKEY_UNKNOWN, // Eisu (JIS Keyboard Only)
+    /* 0x67 */ VKEY_F11,
+    /* 0x68 */ VKEY_UNKNOWN, // Kana (JIS Keyboard Only)
+    /* 0x69 */ VKEY_F13,
+    /* 0x6A */ VKEY_F16,
+    /* 0x6B */ VKEY_F14,
+    /* 0x6C */ VKEY_UNKNOWN, // n/a
+    /* 0x6D */ VKEY_F10,
+    /* 0x6E */ VKEY_UNKNOWN, // n/a (Windows95 key?)
+    /* 0x6F */ VKEY_F12,
+    /* 0x70 */ VKEY_UNKNOWN, // n/a
+    /* 0x71 */ VKEY_F15,
+    /* 0x72 */ VKEY_INSERT, // Help
+    /* 0x73 */ VKEY_HOME, // Home
+    /* 0x74 */ VKEY_PRIOR, // Page Up
+    /* 0x75 */ VKEY_DELETE, // Forward Delete
+    /* 0x76 */ VKEY_F4,
+    /* 0x77 */ VKEY_END, // End
+    /* 0x78 */ VKEY_F2,
+    /* 0x79 */ VKEY_NEXT, // Page Down
+    /* 0x7A */ VKEY_F1,
+    /* 0x7B */ VKEY_LEFT, // Left Arrow
+    /* 0x7C */ VKEY_RIGHT, // Right Arrow
+    /* 0x7D */ VKEY_DOWN, // Down Arrow
+    /* 0x7E */ VKEY_UP, // Up Arrow
+    /* 0x7F */ VKEY_UNKNOWN // n/a
+  };
+
+  if (keyCode >= 0x80)
+    return VKEY_UNKNOWN;
+
+  return kKeyboardCodes[keyCode];
+}
+
+}  // namespace
+
+int MacKeyCodeForWindowsKeyCode(KeyboardCode keycode,
+                                NSUInteger flags,
+                                unichar* character,
+                                unichar* characterIgnoringModifiers) {
+  KeyCodeMap from;
+  from.keycode = keycode;
+
+  const KeyCodeMap* ptr = std::lower_bound(
+      kKeyCodesMap, kKeyCodesMap + arraysize(kKeyCodesMap), from);
+
+  if (ptr >= kKeyCodesMap + arraysize(kKeyCodesMap) ||
+      ptr->keycode != keycode || ptr->macKeycode == -1)
+    return -1;
+
+  int macKeycode = ptr->macKeycode;
+  if (characterIgnoringModifiers)
+    *characterIgnoringModifiers = ptr->characterIgnoringModifiers;
+
+  if (!character)
+    return macKeycode;
+
+  *character = ptr->characterIgnoringModifiers;
+
+  // Fill in |character| according to flags.
+  if (flags & NSShiftKeyMask) {
+    if (keycode >= VKEY_0 && keycode <= VKEY_9) {
+      *character = kShiftCharsForNumberKeys[keycode - VKEY_0];
+    } else if (keycode >= VKEY_A && keycode <= VKEY_Z) {
+      *character = 'A' + (keycode - VKEY_A);
+    } else {
+      switch (macKeycode) {
+        case kVK_ANSI_Grave:
+          *character = '~';
+          break;
+        case kVK_ANSI_Minus:
+          *character = '_';
+          break;
+        case kVK_ANSI_Equal:
+          *character = '+';
+          break;
+        case kVK_ANSI_LeftBracket:
+          *character = '{';
+          break;
+        case kVK_ANSI_RightBracket:
+          *character = '}';
+          break;
+        case kVK_ANSI_Backslash:
+          *character = '|';
+          break;
+        case kVK_ANSI_Semicolon:
+          *character = ':';
+          break;
+        case kVK_ANSI_Quote:
+          *character = '\"';
+          break;
+        case kVK_ANSI_Comma:
+          *character = '<';
+          break;
+        case kVK_ANSI_Period:
+          *character = '>';
+          break;
+        case kVK_ANSI_Slash:
+          *character = '?';
+          break;
+        default:
+          break;
+      }
+    }
+  }
+
+  // TODO(suzhe): Support characters for Option key bindings.
+  return macKeycode;
+}
+
+KeyboardCode KeyboardCodeFromNSEvent(NSEvent* event) {
+  KeyboardCode code = VKEY_UNKNOWN;
+
+  if ([event type] == NSKeyDown || [event type] == NSKeyUp) {
+    NSString* characters = [event characters];
+    if ([characters length] > 0)
+      code = KeyboardCodeFromCharCode([characters characterAtIndex:0]);
+    if (code)
+      return code;
+
+    characters = [event charactersIgnoringModifiers];
+    if ([characters length] > 0)
+      code = KeyboardCodeFromCharCode([characters characterAtIndex:0]);
+    if (code)
+      return code;
+  }
+  return KeyboardCodeFromKeyCode([event keyCode]);
+}
+
+const char* CodeFromNSEvent(NSEvent* event) {
+  return ui::KeycodeConverter::NativeKeycodeToCode([event keyCode]);
+}
+
+}  // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_win.cc b/ui/events/keycodes/keyboard_code_conversion_win.cc
new file mode 100644
index 0000000..262658d
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_win.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 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 "ui/events/keycodes/keyboard_code_conversion_win.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+
+namespace ui {
+
+WORD WindowsKeyCodeForKeyboardCode(KeyboardCode keycode) {
+  return static_cast<WORD>(keycode);
+}
+
+KeyboardCode KeyboardCodeForWindowsKeyCode(WORD keycode) {
+  return static_cast<KeyboardCode>(keycode);
+}
+
+const char* CodeForWindowsScanCode(WORD scan_code) {
+  return ui::KeycodeConverter::NativeKeycodeToCode(scan_code);
+}
+
+}  // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_win.h b/ui/events/keycodes/keyboard_code_conversion_win.h
new file mode 100644
index 0000000..4f09a2a
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_win.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2011 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 UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_WIN_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_WIN_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+
+namespace ui {
+
+// Methods to convert ui::KeyboardCode/Windows virtual key type methods.
+EVENTS_BASE_EXPORT WORD WindowsKeyCodeForKeyboardCode(KeyboardCode keycode);
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeForWindowsKeyCode(WORD keycode);
+EVENTS_BASE_EXPORT const char* CodeForWindowsScanCode(WORD scan_code);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_WIN_H_
diff --git a/ui/events/keycodes/keyboard_code_conversion_x.cc b/ui/events/keycodes/keyboard_code_conversion_x.cc
new file mode 100644
index 0000000..4311a96
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_x.cc
@@ -0,0 +1,1356 @@
+// Copyright (c) 2012 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 "ui/events/keycodes/keyboard_code_conversion_x.h"
+
+#include <algorithm>
+
+#define XK_3270  // for XK_3270_BackTab
+#include <X11/XF86keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/keysym.h>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+#include "ui/events/x/keysym_to_unicode.h"
+
+#define VKEY_UNSUPPORTED VKEY_UNKNOWN
+
+namespace ui {
+
+namespace {
+
+// MAP0 - MAP3:
+// These are the generated VKEY code maps for all possible Latin keyboard
+// layouts in Windows. And the maps are only for special letter keys excluding
+// [a-z] & [0-9].
+//
+// ch0: the keysym without modifier states.
+// ch1: the keysym with shift state.
+// ch2: the keysym with altgr state.
+// sc: the hardware keycode (in Windows, it's called scan code).
+// vk: the VKEY code.
+//
+// MAP0: maps from ch0 to vk.
+// MAP1: maps from ch0+sc to vk.
+// MAP2: maps from ch0+ch1+sc to vk.
+// MAP3: maps from ch0+ch1+ch2+sc to vk.
+// MAP0 - MAP3 are all sorted, so that finding VK can be binary search.
+//
+// Reason for creating these maps is because a hard-coded mapping in
+// KeyboardCodeFromXKeysym() doesn't support non-US keyboard layouts.
+// e.g. in UK keyboard, the key between Quote and Enter keys has the VKEY code
+// VKEY_OEM_5 instead of VKEY_3.
+//
+// The key symbols which are not [a-zA-Z0-9] and functional/extend keys (e.g.
+// TAB, ENTER, BS, Arrow keys, modifier keys, F1-F12, media/app keys, etc.)
+// should go through these maps for correct VKEY codes.
+//
+// Please refer to crbug.com/386066.
+//
+const struct MAP0 {
+  KeySym ch0;
+  uint8 vk;
+  bool operator()(const MAP0& m1, const MAP0& m2) const {
+    return m1.ch0 < m2.ch0;
+  }
+} map0[] = {
+      {0x0025, 0x35},  // XK_percent: VKEY_5
+      {0x0026, 0x31},  // XK_ampersand: VKEY_1
+      {0x003C, 0xDC},  // XK_less: VKEY_OEM_5
+      {0x007B, 0xDE},  // XK_braceleft: VKEY_OEM_7
+      {0x007C, 0xDC},  // XK_bar: VKEY_OEM_5
+      {0x007D, 0xBF},  // XK_braceright: VKEY_OEM_2
+      {0x007E, 0xDC},  // XK_asciitilde: VKEY_OEM_5
+      {0x00A1, 0xDD},  // XK_exclamdown: VKEY_OEM_6
+      {0x00AD, 0xC0},  // XK_hyphen: VKEY_OEM_3
+      {0x00B2, 0xDE},  // XK_twosuperior: VKEY_OEM_7
+      {0x00B5, 0xDC},  // XK_mu: VKEY_OEM_5
+      {0x00BB, 0x39},  // XK_guillemotright: VKEY_9
+      {0x00BD, 0xDC},  // XK_onehalf: VKEY_OEM_5
+      {0x00BF, 0xDD},  // XK_questiondown: VKEY_OEM_6
+      {0x00DF, 0xDB},  // XK_ssharp: VKEY_OEM_4
+      {0x00E5, 0xDD},  // XK_aring: VKEY_OEM_6
+      {0x00EA, 0x33},  // XK_ecircumflex: VKEY_3
+      {0x00EB, 0xBA},  // XK_ediaeresis: VKEY_OEM_1
+      {0x00EC, 0xDD},  // XK_igrave: VKEY_OEM_6
+      {0x00EE, 0xDD},  // XK_icircumflex: VKEY_OEM_6
+      {0x00F1, 0xC0},  // XK_ntilde: VKEY_OEM_3
+      {0x00F2, 0xC0},  // XK_ograve: VKEY_OEM_3
+      {0x00F5, 0xDB},  // XK_otilde: VKEY_OEM_4
+      {0x00F7, 0xDD},  // XK_division: VKEY_OEM_6
+      {0x00FD, 0x37},  // XK_yacute: VKEY_7
+      {0x00FE, 0xBD},  // XK_thorn: VKEY_OEM_MINUS
+      {0x01A1, 0xDD},  // XK_ohorn: VKEY_OEM_6
+      {0x01B0, 0xDB},  // XK_uhorn: VKEY_OEM_4
+      {0x01B5, 0x32},  // XK_lcaron: VKEY_2
+      {0x01B6, 0xDD},  // XK_zstroke: VKEY_OEM_6
+      {0x01BB, 0x35},  // XK_tcaron: VKEY_5
+      {0x01E6, 0xDE},  // XK_cacute: VKEY_OEM_7
+      {0x01EC, 0x32},  // XK_ecaron: VKEY_2
+      {0x01F2, 0xDC},  // XK_ncaron: VKEY_OEM_5
+      {0x01F5, 0xDB},  // XK_odoubleacute: VKEY_OEM_4
+      {0x01F8, 0x35},  // XK_rcaron: VKEY_5
+      {0x01F9, 0xBA},  // XK_uring: VKEY_OEM_1
+      {0x01FB, 0xDC},  // XK_udoubleacute: VKEY_OEM_5
+      {0x01FE, 0xDE},  // XK_tcedilla: VKEY_OEM_7
+      {0x0259, 0xC0},  // XK_schwa: VKEY_OEM_3
+      {0x02B1, 0xDD},  // XK_hstroke: VKEY_OEM_6
+      {0x02B9, 0xBA},  // XK_idotless: VKEY_OEM_1
+      {0x02BB, 0xDD},  // XK_gbreve: VKEY_OEM_6
+      {0x02E5, 0xC0},  // XK_cabovedot: VKEY_OEM_3
+      {0x02F5, 0xDB},  // XK_gabovedot: VKEY_OEM_4
+      {0x03B6, 0xBF},  // XK_lcedilla: VKEY_OEM_2
+      {0x03BA, 0x57},  // XK_emacron: VKEY_W
+      {0x03E0, 0xDF},  // XK_amacron: VKEY_OEM_8
+      {0x03EF, 0xDD},  // XK_imacron: VKEY_OEM_6
+      {0x03F1, 0xDB},  // XK_ncedilla: VKEY_OEM_4
+      {0x03F3, 0xDC},  // XK_kcedilla: VKEY_OEM_5
+};
+
+const struct MAP1 {
+  KeySym ch0;
+  unsigned sc;
+  uint8 vk;
+  bool operator()(const MAP1& m1, const MAP1& m2) const {
+    if (m1.ch0 == m2.ch0)
+      return m1.sc < m2.sc;
+    return m1.ch0 < m2.ch0;
+  }
+} map1[] = {
+      {0x0021, 0x0A, 0x31},  // XK_exclam+AE01: VKEY_1
+      {0x0021, 0x11, 0x38},  // XK_exclam+AE08: VKEY_8
+      {0x0021, 0x3D, 0xDF},  // XK_exclam+AB10: VKEY_OEM_8
+      {0x0022, 0x0B, 0x32},  // XK_quotedbl+AE02: VKEY_2
+      {0x0022, 0x0C, 0x33},  // XK_quotedbl+AE03: VKEY_3
+      {0x0023, 0x31, 0xDE},  // XK_numbersign+TLDE: VKEY_OEM_7
+      {0x0024, 0x23, 0xBA},  // XK_dollar+AD12: VKEY_OEM_1
+      {0x0024, 0x33, 0xDF},  // XK_dollar+BKSL: VKEY_OEM_8
+      {0x0027, 0x0D, 0x34},  // XK_quoteright+AE04: VKEY_4
+      {0x0027, 0x18, 0xDE},  // XK_quoteright+AD01: VKEY_OEM_7
+      {0x0027, 0x23, 0xBA},  // XK_quoteright+AD12: VKEY_OEM_1
+      {0x0027, 0x3D, 0xDE},  // XK_quoteright+AB10: VKEY_OEM_7
+      {0x0028, 0x0E, 0x35},  // XK_parenleft+AE05: VKEY_5
+      {0x0028, 0x12, 0x39},  // XK_parenleft+AE09: VKEY_9
+      {0x0028, 0x33, 0xDC},  // XK_parenleft+BKSL: VKEY_OEM_5
+      {0x0029, 0x13, 0x30},  // XK_parenright+AE10: VKEY_0
+      {0x0029, 0x14, 0xDB},  // XK_parenright+AE11: VKEY_OEM_4
+      {0x0029, 0x23, 0xDD},  // XK_parenright+AD12: VKEY_OEM_6
+      {0x002A, 0x23, 0xBA},  // XK_asterisk+AD12: VKEY_OEM_1
+      {0x002A, 0x33, 0xDC},  // XK_asterisk+BKSL: VKEY_OEM_5
+      {0x002B, 0x0A, 0x31},  // XK_plus+AE01: VKEY_1
+      {0x002B, 0x15, 0xBB},  // XK_plus+AE12: VKEY_OEM_PLUS
+      {0x002B, 0x22, 0xBB},  // XK_plus+AD11: VKEY_OEM_PLUS
+      {0x002B, 0x23, 0xBB},  // XK_plus+AD12: VKEY_OEM_PLUS
+      {0x002B, 0x2F, 0xBB},  // XK_plus+AC10: VKEY_OEM_PLUS
+      {0x002B, 0x33, 0xBF},  // XK_plus+BKSL: VKEY_OEM_2
+      {0x002C, 0x0C, 0x33},  // XK_comma+AE03: VKEY_3
+      {0x002C, 0x0E, 0x35},  // XK_comma+AE05: VKEY_5
+      {0x002C, 0x0F, 0x36},  // XK_comma+AE06: VKEY_6
+      {0x002C, 0x12, 0x39},  // XK_comma+AE09: VKEY_9
+      {0x002C, 0x19, 0xBC},  // XK_comma+AD02: VKEY_OEM_COMMA
+      {0x002C, 0x37, 0xBC},  // XK_comma+AB04: VKEY_OEM_COMMA
+      {0x002C, 0x3A, 0xBC},  // XK_comma+AB07: VKEY_OEM_COMMA
+      {0x002C, 0x3B, 0xBC},  // XK_comma+AB08: VKEY_OEM_COMMA
+      {0x002D, 0x0B, 0x32},  // XK_minus+AE02: VKEY_2
+      {0x002D, 0x0F, 0x36},  // XK_minus+AE06: VKEY_6
+      {0x002D, 0x14, 0xBD},  // XK_minus+AE11: VKEY_OEM_MINUS
+      {0x002D, 0x26, 0xBD},  // XK_minus+AC01: VKEY_OEM_MINUS
+      {0x002D, 0x30, 0xBD},  // XK_minus+AC11: VKEY_OEM_MINUS
+      {0x002E, 0x10, 0x37},  // XK_period+AE07: VKEY_7
+      {0x002E, 0x11, 0x38},  // XK_period+AE08: VKEY_8
+      {0x002E, 0x1A, 0xBE},  // XK_period+AD03: VKEY_OEM_PERIOD
+      {0x002E, 0x1B, 0xBE},  // XK_period+AD04: VKEY_OEM_PERIOD
+      {0x002E, 0x20, 0xBE},  // XK_period+AD09: VKEY_OEM_PERIOD
+      {0x002E, 0x30, 0xDE},  // XK_period+AC11: VKEY_OEM_7
+      {0x002E, 0x3C, 0xBE},  // XK_period+AB09: VKEY_OEM_PERIOD
+      {0x002E, 0x3D, 0xBF},  // XK_period+AB10: VKEY_OEM_2
+      {0x002F, 0x14, 0xDB},  // XK_slash+AE11: VKEY_OEM_4
+      {0x002F, 0x22, 0xBF},  // XK_slash+AD11: VKEY_OEM_2
+      {0x002F, 0x31, 0xDE},  // XK_slash+TLDE: VKEY_OEM_7
+      {0x002F, 0x33, 0xDC},  // XK_slash+BKSL: VKEY_OEM_5
+      {0x002F, 0x3D, 0xBF},  // XK_slash+AB10: VKEY_OEM_2
+      {0x003A, 0x0A, 0x31},  // XK_colon+AE01: VKEY_1
+      {0x003A, 0x0E, 0x35},  // XK_colon+AE05: VKEY_5
+      {0x003A, 0x0F, 0x36},  // XK_colon+AE06: VKEY_6
+      {0x003A, 0x3C, 0xBF},  // XK_colon+AB09: VKEY_OEM_2
+      {0x003B, 0x0D, 0x34},  // XK_semicolon+AE04: VKEY_4
+      {0x003B, 0x11, 0x38},  // XK_semicolon+AE08: VKEY_8
+      {0x003B, 0x18, 0xBA},  // XK_semicolon+AD01: VKEY_OEM_1
+      {0x003B, 0x22, 0xBA},  // XK_semicolon+AD11: VKEY_OEM_1
+      {0x003B, 0x23, 0xDD},  // XK_semicolon+AD12: VKEY_OEM_6
+      {0x003B, 0x2F, 0xBA},  // XK_semicolon+AC10: VKEY_OEM_1
+      {0x003B, 0x31, 0xC0},  // XK_semicolon+TLDE: VKEY_OEM_3
+      {0x003B, 0x34, 0xBA},  // XK_semicolon+AB01: VKEY_OEM_1
+      {0x003B, 0x3B, 0xBE},  // XK_semicolon+AB08: VKEY_OEM_PERIOD
+      {0x003B, 0x3D, 0xBF},  // XK_semicolon+AB10: VKEY_OEM_2
+      {0x003D, 0x11, 0x38},  // XK_equal+AE08: VKEY_8
+      {0x003D, 0x15, 0xBB},  // XK_equal+AE12: VKEY_OEM_PLUS
+      {0x003D, 0x23, 0xBB},  // XK_equal+AD12: VKEY_OEM_PLUS
+      {0x003F, 0x0B, 0x32},  // XK_question+AE02: VKEY_2
+      {0x003F, 0x10, 0x37},  // XK_question+AE07: VKEY_7
+      {0x003F, 0x11, 0x38},  // XK_question+AE08: VKEY_8
+      {0x003F, 0x14, 0xBB},  // XK_question+AE11: VKEY_OEM_PLUS
+      {0x0040, 0x23, 0xDD},  // XK_at+AD12: VKEY_OEM_6
+      {0x0040, 0x31, 0xDE},  // XK_at+TLDE: VKEY_OEM_7
+      {0x005B, 0x0A, 0xDB},  // XK_bracketleft+AE01: VKEY_OEM_4
+      {0x005B, 0x14, 0xDB},  // XK_bracketleft+AE11: VKEY_OEM_4
+      {0x005B, 0x22, 0xDB},  // XK_bracketleft+AD11: VKEY_OEM_4
+      {0x005B, 0x23, 0xDD},  // XK_bracketleft+AD12: VKEY_OEM_6
+      {0x005B, 0x30, 0xDE},  // XK_bracketleft+AC11: VKEY_OEM_7
+      {0x005C, 0x15, 0xDB},  // XK_backslash+AE12: VKEY_OEM_4
+      {0x005D, 0x0B, 0xDD},  // XK_bracketright+AE02: VKEY_OEM_6
+      {0x005D, 0x15, 0xDD},  // XK_bracketright+AE12: VKEY_OEM_6
+      {0x005D, 0x23, 0xDD},  // XK_bracketright+AD12: VKEY_OEM_6
+      {0x005D, 0x31, 0xC0},  // XK_bracketright+TLDE: VKEY_OEM_3
+      {0x005D, 0x33, 0xDC},  // XK_bracketright+BKSL: VKEY_OEM_5
+      {0x005F, 0x11, 0x38},  // XK_underscore+AE08: VKEY_8
+      {0x005F, 0x14, 0xBD},  // XK_underscore+AE11: VKEY_OEM_MINUS
+      {0x00A7, 0x0D, 0x34},  // XK_section+AE04: VKEY_4
+      {0x00A7, 0x0F, 0x36},  // XK_section+AE06: VKEY_6
+      {0x00A7, 0x30, 0xDE},  // XK_section+AC11: VKEY_OEM_7
+      {0x00AB, 0x11, 0x38},  // XK_guillemotleft+AE08: VKEY_8
+      {0x00AB, 0x15, 0xDD},  // XK_guillemotleft+AE12: VKEY_OEM_6
+      {0x00B0, 0x15, 0xBF},  // XK_degree+AE12: VKEY_OEM_2
+      {0x00B0, 0x31, 0xDE},  // XK_degree+TLDE: VKEY_OEM_7
+      {0x00BA, 0x30, 0xDE},  // XK_masculine+AC11: VKEY_OEM_7
+      {0x00BA, 0x31, 0xDC},  // XK_masculine+TLDE: VKEY_OEM_5
+      {0x00E0, 0x13, 0x30},  // XK_agrave+AE10: VKEY_0
+      {0x00E0, 0x33, 0xDC},  // XK_agrave+BKSL: VKEY_OEM_5
+      {0x00E1, 0x11, 0x38},  // XK_aacute+AE08: VKEY_8
+      {0x00E1, 0x30, 0xDE},  // XK_aacute+AC11: VKEY_OEM_7
+      {0x00E2, 0x0B, 0x32},  // XK_acircumflex+AE02: VKEY_2
+      {0x00E2, 0x33, 0xDC},  // XK_acircumflex+BKSL: VKEY_OEM_5
+      {0x00E4, 0x23, 0xDD},  // XK_adiaeresis+AD12: VKEY_OEM_6
+      {0x00E6, 0x2F, 0xC0},  // XK_ae+AC10: VKEY_OEM_3
+      {0x00E6, 0x30, 0xDE},  // XK_ae+AC11: VKEY_OEM_7
+      {0x00E7, 0x12, 0x39},  // XK_ccedilla+AE09: VKEY_9
+      {0x00E7, 0x22, 0xDB},  // XK_ccedilla+AD11: VKEY_OEM_4
+      {0x00E7, 0x23, 0xDD},  // XK_ccedilla+AD12: VKEY_OEM_6
+      {0x00E7, 0x30, 0xDE},  // XK_ccedilla+AC11: VKEY_OEM_7
+      {0x00E7, 0x33, 0xBF},  // XK_ccedilla+BKSL: VKEY_OEM_2
+      {0x00E7, 0x3B, 0xBC},  // XK_ccedilla+AB08: VKEY_OEM_COMMA
+      {0x00E8, 0x10, 0x37},  // XK_egrave+AE07: VKEY_7
+      {0x00E8, 0x22, 0xBA},  // XK_egrave+AD11: VKEY_OEM_1
+      {0x00E8, 0x30, 0xC0},  // XK_egrave+AC11: VKEY_OEM_3
+      {0x00E9, 0x0B, 0x32},  // XK_eacute+AE02: VKEY_2
+      {0x00E9, 0x13, 0x30},  // XK_eacute+AE10: VKEY_0
+      {0x00E9, 0x3D, 0xBF},  // XK_eacute+AB10: VKEY_OEM_2
+      {0x00ED, 0x12, 0x39},  // XK_iacute+AE09: VKEY_9
+      {0x00ED, 0x31, 0x30},  // XK_iacute+TLDE: VKEY_0
+      {0x00F0, 0x22, 0xDD},  // XK_eth+AD11: VKEY_OEM_6
+      {0x00F0, 0x23, 0xBA},  // XK_eth+AD12: VKEY_OEM_1
+      {0x00F3, 0x15, 0xBB},  // XK_oacute+AE12: VKEY_OEM_PLUS
+      {0x00F3, 0x33, 0xDC},  // XK_oacute+BKSL: VKEY_OEM_5
+      {0x00F4, 0x0D, 0x34},  // XK_ocircumflex+AE04: VKEY_4
+      {0x00F4, 0x2F, 0xBA},  // XK_ocircumflex+AC10: VKEY_OEM_1
+      {0x00F6, 0x13, 0xC0},  // XK_odiaeresis+AE10: VKEY_OEM_3
+      {0x00F6, 0x14, 0xBB},  // XK_odiaeresis+AE11: VKEY_OEM_PLUS
+      {0x00F6, 0x22, 0xDB},  // XK_odiaeresis+AD11: VKEY_OEM_4
+      {0x00F8, 0x2F, 0xC0},  // XK_oslash+AC10: VKEY_OEM_3
+      {0x00F8, 0x30, 0xDE},  // XK_oslash+AC11: VKEY_OEM_7
+      {0x00F9, 0x30, 0xC0},  // XK_ugrave+AC11: VKEY_OEM_3
+      {0x00F9, 0x33, 0xBF},  // XK_ugrave+BKSL: VKEY_OEM_2
+      {0x00FA, 0x22, 0xDB},  // XK_uacute+AD11: VKEY_OEM_4
+      {0x00FA, 0x23, 0xDD},  // XK_uacute+AD12: VKEY_OEM_6
+      {0x00FC, 0x19, 0x57},  // XK_udiaeresis+AD02: VKEY_W
+      {0x01B1, 0x0A, 0x31},  // XK_aogonek+AE01: VKEY_1
+      {0x01B1, 0x18, 0x51},  // XK_aogonek+AD01: VKEY_Q
+      {0x01B1, 0x30, 0xDE},  // XK_aogonek+AC11: VKEY_OEM_7
+      {0x01B3, 0x2F, 0xBA},  // XK_lstroke+AC10: VKEY_OEM_1
+      {0x01B3, 0x33, 0xBF},  // XK_lstroke+BKSL: VKEY_OEM_2
+      {0x01B9, 0x0C, 0x33},  // XK_scaron+AE03: VKEY_3
+      {0x01B9, 0x0F, 0x36},  // XK_scaron+AE06: VKEY_6
+      {0x01B9, 0x22, 0xDB},  // XK_scaron+AD11: VKEY_OEM_4
+      {0x01B9, 0x26, 0xBA},  // XK_scaron+AC01: VKEY_OEM_1
+      {0x01B9, 0x29, 0x46},  // XK_scaron+AC04: VKEY_F
+      {0x01B9, 0x3C, 0xBE},  // XK_scaron+AB09: VKEY_OEM_PERIOD
+      {0x01BA, 0x2F, 0xBA},  // XK_scedilla+AC10: VKEY_OEM_1
+      {0x01BA, 0x3C, 0xBE},  // XK_scedilla+AB09: VKEY_OEM_PERIOD
+      {0x01BE, 0x0F, 0x36},  // XK_zcaron+AE06: VKEY_6
+      {0x01BE, 0x15, 0xBB},  // XK_zcaron+AE12: VKEY_OEM_PLUS
+      {0x01BE, 0x19, 0x57},  // XK_zcaron+AD02: VKEY_W
+      {0x01BE, 0x22, 0x59},  // XK_zcaron+AD11: VKEY_Y
+      {0x01BE, 0x33, 0xDC},  // XK_zcaron+BKSL: VKEY_OEM_5
+      {0x01BF, 0x22, 0xDB},  // XK_zabovedot+AD11: VKEY_OEM_4
+      {0x01BF, 0x33, 0xDC},  // XK_zabovedot+BKSL: VKEY_OEM_5
+      {0x01E3, 0x0A, 0x31},  // XK_abreve+AE01: VKEY_1
+      {0x01E3, 0x22, 0xDB},  // XK_abreve+AD11: VKEY_OEM_4
+      {0x01E8, 0x0B, 0x32},  // XK_ccaron+AE02: VKEY_2
+      {0x01E8, 0x0D, 0x34},  // XK_ccaron+AE04: VKEY_4
+      {0x01E8, 0x21, 0x58},  // XK_ccaron+AD10: VKEY_X
+      {0x01E8, 0x2F, 0xBA},  // XK_ccaron+AC10: VKEY_OEM_1
+      {0x01E8, 0x3B, 0xBC},  // XK_ccaron+AB08: VKEY_OEM_COMMA
+      {0x01EA, 0x0C, 0x33},  // XK_eogonek+AE03: VKEY_3
+      {0x01F0, 0x13, 0x30},  // XK_dstroke+AE10: VKEY_0
+      {0x01F0, 0x23, 0xDD},  // XK_dstroke+AD12: VKEY_OEM_6
+      {0x03E7, 0x0E, 0x35},  // XK_iogonek+AE05: VKEY_5
+      {0x03EC, 0x0D, 0x34},  // XK_eabovedot+AE04: VKEY_4
+      {0x03EC, 0x30, 0xDE},  // XK_eabovedot+AC11: VKEY_OEM_7
+      {0x03F9, 0x10, 0x37},  // XK_uogonek+AE07: VKEY_7
+      {0x03FE, 0x11, 0x38},  // XK_umacron+AE08: VKEY_8
+      {0x03FE, 0x18, 0x51},  // XK_umacron+AD01: VKEY_Q
+      {0x03FE, 0x35, 0x58},  // XK_umacron+AB02: VKEY_X
+};
+
+const struct MAP2 {
+  KeySym ch0;
+  unsigned sc;
+  KeySym ch1;
+  uint8 vk;
+  bool operator()(const MAP2& m1, const MAP2& m2) const {
+    if (m1.ch0 == m2.ch0 && m1.sc == m2.sc)
+      return m1.ch1 < m2.ch1;
+    if (m1.ch0 == m2.ch0)
+      return m1.sc < m2.sc;
+    return m1.ch0 < m2.ch0;
+  }
+} map2[] = {
+      {0x0023, 0x33, 0x0027,
+       0xBF},  // XK_numbersign+BKSL+XK_quoteright: VKEY_OEM_2
+      {0x0027, 0x30, 0x0022,
+       0xDE},  // XK_quoteright+AC11+XK_quotedbl: VKEY_OEM_7
+      {0x0027, 0x31, 0x0022,
+       0xC0},  // XK_quoteright+TLDE+XK_quotedbl: VKEY_OEM_3
+      {0x0027, 0x31, 0x00B7,
+       0xDC},  // XK_quoteright+TLDE+XK_periodcentered: VKEY_OEM_5
+      {0x0027, 0x33, 0x0000, 0xDC},  // XK_quoteright+BKSL+NoSymbol: VKEY_OEM_5
+      {0x002D, 0x3D, 0x003D, 0xBD},  // XK_minus+AB10+XK_equal: VKEY_OEM_MINUS
+      {0x002F, 0x0C, 0x0033, 0x33},  // XK_slash+AE03+XK_3: VKEY_3
+      {0x002F, 0x0C, 0x003F, 0xBF},  // XK_slash+AE03+XK_question: VKEY_OEM_2
+      {0x002F, 0x13, 0x0030, 0x30},  // XK_slash+AE10+XK_0: VKEY_0
+      {0x002F, 0x13, 0x003F, 0xBF},  // XK_slash+AE10+XK_question: VKEY_OEM_2
+      {0x003D, 0x3D, 0x0025, 0xDF},  // XK_equal+AB10+XK_percent: VKEY_OEM_8
+      {0x003D, 0x3D, 0x002B, 0xBB},  // XK_equal+AB10+XK_plus: VKEY_OEM_PLUS
+      {0x005C, 0x33, 0x002F, 0xDE},  // XK_backslash+BKSL+XK_slash: VKEY_OEM_7
+      {0x005C, 0x33, 0x007C, 0xDC},  // XK_backslash+BKSL+XK_bar: VKEY_OEM_5
+      {0x0060, 0x31, 0x0000, 0xC0},  // XK_quoteleft+TLDE+NoSymbol: VKEY_OEM_3
+      {0x0060, 0x31, 0x00AC, 0xDF},  // XK_quoteleft+TLDE+XK_notsign: VKEY_OEM_8
+      {0x00A7, 0x31, 0x00B0, 0xBF},  // XK_section+TLDE+XK_degree: VKEY_OEM_2
+      {0x00A7, 0x31, 0x00BD, 0xDC},  // XK_section+TLDE+XK_onehalf: VKEY_OEM_5
+      {0x00E0, 0x30, 0x00B0, 0xDE},  // XK_agrave+AC11+XK_degree: VKEY_OEM_7
+      {0x00E0, 0x30, 0x00E4, 0xDC},  // XK_agrave+AC11+XK_adiaeresis: VKEY_OEM_5
+      {0x00E4, 0x30, 0x00E0, 0xDC},  // XK_adiaeresis+AC11+XK_agrave: VKEY_OEM_5
+      {0x00E9, 0x2F, 0x00C9, 0xBA},  // XK_eacute+AC10+XK_Eacute: VKEY_OEM_1
+      {0x00E9, 0x2F, 0x00F6, 0xDE},  // XK_eacute+AC10+XK_odiaeresis: VKEY_OEM_7
+      {0x00F6, 0x2F, 0x00E9, 0xDE},  // XK_odiaeresis+AC10+XK_eacute: VKEY_OEM_7
+      {0x00FC, 0x22, 0x00E8, 0xBA},  // XK_udiaeresis+AD11+XK_egrave: VKEY_OEM_1
+};
+
+const struct MAP3 {
+  KeySym ch0;
+  unsigned sc;
+  KeySym ch1;
+  KeySym ch2;
+  uint8 vk;
+  bool operator()(const MAP3& m1, const MAP3& m2) const {
+    if (m1.ch0 == m2.ch0 && m1.sc == m2.sc && m1.ch1 == m2.ch1)
+      return m1.ch2 < m2.ch2;
+    if (m1.ch0 == m2.ch0 && m1.sc == m2.sc)
+      return m1.ch1 < m2.ch1;
+    if (m1.ch0 == m2.ch0)
+      return m1.sc < m2.sc;
+    return m1.ch0 < m2.ch0;
+  }
+} map3[] = {
+      {0x0023, 0x33, 0x007E, 0x0000,
+       0xDE},  // XK_numbersign+BKSL+XK_asciitilde+NoSymbol: VKEY_OEM_7
+      {0x0027, 0x14, 0x003F, 0x0000,
+       0xDB},  // XK_quoteright+AE11+XK_question+NoSymbol: VKEY_OEM_4
+      {0x0027, 0x14, 0x003F, 0x00DD,
+       0xDB},  // XK_quoteright+AE11+XK_question+XK_Yacute: VKEY_OEM_4
+      {0x0027, 0x15, 0x002A, 0x0000,
+       0xBB},  // XK_quoteright+AE12+XK_asterisk+NoSymbol: VKEY_OEM_PLUS
+      {0x0027, 0x30, 0x0040, 0x0000,
+       0xC0},  // XK_quoteright+AC11+XK_at+NoSymbol: VKEY_OEM_3
+      {0x0027, 0x33, 0x002A, 0x0000,
+       0xBF},  // XK_quoteright+BKSL+XK_asterisk+NoSymbol: VKEY_OEM_2
+      {0x0027, 0x33, 0x002A, 0x00BD,
+       0xDC},  // XK_quoteright+BKSL+XK_asterisk+XK_onehalf: VKEY_OEM_5
+      {0x0027, 0x33, 0x002A, 0x01A3,
+       0xBF},  // XK_quoteright+BKSL+XK_asterisk+XK_Lstroke: VKEY_OEM_2
+      {0x0027, 0x34, 0x0022, 0x0000,
+       0x5A},  // XK_quoteright+AB01+XK_quotedbl+NoSymbol: VKEY_Z
+      {0x0027, 0x34, 0x0022, 0x01D8,
+       0xDE},  // XK_quoteright+AB01+XK_quotedbl+XK_Rcaron: VKEY_OEM_7
+      {0x002B, 0x14, 0x003F, 0x0000,
+       0xBB},  // XK_plus+AE11+XK_question+NoSymbol: VKEY_OEM_PLUS
+      {0x002B, 0x14, 0x003F, 0x005C,
+       0xBD},  // XK_plus+AE11+XK_question+XK_backslash: VKEY_OEM_MINUS
+      {0x002B, 0x14, 0x003F, 0x01F5,
+       0xBB},  // XK_plus+AE11+XK_question+XK_odoubleacute: VKEY_OEM_PLUS
+      {0x002D, 0x15, 0x005F, 0x0000,
+       0xBD},  // XK_minus+AE12+XK_underscore+NoSymbol: VKEY_OEM_MINUS
+      {0x002D, 0x15, 0x005F, 0x03B3,
+       0xDB},  // XK_minus+AE12+XK_underscore+XK_rcedilla: VKEY_OEM_4
+      {0x002D, 0x3D, 0x005F, 0x0000,
+       0xBD},  // XK_minus+AB10+XK_underscore+NoSymbol: VKEY_OEM_MINUS
+      {0x002D, 0x3D, 0x005F, 0x002A,
+       0xBD},  // XK_minus+AB10+XK_underscore+XK_asterisk: VKEY_OEM_MINUS
+      {0x002D, 0x3D, 0x005F, 0x002F,
+       0xBF},  // XK_minus+AB10+XK_underscore+XK_slash: VKEY_OEM_2
+      {0x002D, 0x3D, 0x005F, 0x006E,
+       0xBD},  // XK_minus+AB10+XK_underscore+XK_n: VKEY_OEM_MINUS
+      {0x003D, 0x14, 0x0025, 0x0000,
+       0xBB},  // XK_equal+AE11+XK_percent+NoSymbol: VKEY_OEM_PLUS
+      {0x003D, 0x14, 0x0025, 0x002D,
+       0xBD},  // XK_equal+AE11+XK_percent+XK_minus: VKEY_OEM_MINUS
+      {0x005C, 0x31, 0x007C, 0x0031,
+       0xDC},  // XK_backslash+TLDE+XK_bar+XK_1: VKEY_OEM_5
+      {0x005C, 0x31, 0x007C, 0x03D1,
+       0xC0},  // XK_backslash+TLDE+XK_bar+XK_Ncedilla: VKEY_OEM_3
+      {0x0060, 0x31, 0x007E, 0x0000,
+       0xC0},  // XK_quoteleft+TLDE+XK_asciitilde+NoSymbol: VKEY_OEM_3
+      {0x0060, 0x31, 0x007E, 0x0031,
+       0xC0},  // XK_quoteleft+TLDE+XK_asciitilde+XK_1: VKEY_OEM_3
+      {0x0060, 0x31, 0x007E, 0x003B,
+       0xC0},  // XK_quoteleft+TLDE+XK_asciitilde+XK_semicolon: VKEY_OEM_3
+      {0x0060, 0x31, 0x007E, 0x0060,
+       0xC0},  // XK_quoteleft+TLDE+XK_asciitilde+XK_quoteleft: VKEY_OEM_3
+      {0x0060, 0x31, 0x007E, 0x00BF,
+       0xC0},  // XK_quoteleft+TLDE+XK_asciitilde+XK_questiondown: VKEY_OEM_3
+      {0x0060, 0x31, 0x007E, 0x01F5,
+       0xC0},  // XK_quoteleft+TLDE+XK_asciitilde+XK_odoubleacute: VKEY_OEM_3
+      {0x00E4, 0x30, 0x00C4, 0x0000,
+       0xDE},  // XK_adiaeresis+AC11+XK_Adiaeresis+NoSymbol: VKEY_OEM_7
+      {0x00E4, 0x30, 0x00C4, 0x01A6,
+       0xDE},  // XK_adiaeresis+AC11+XK_Adiaeresis+XK_Sacute: VKEY_OEM_7
+      {0x00E4, 0x30, 0x00C4, 0x01F8,
+       0xDE},  // XK_adiaeresis+AC11+XK_Adiaeresis+XK_rcaron: VKEY_OEM_7
+      {0x00E7, 0x2F, 0x00C7, 0x0000,
+       0xBA},  // XK_ccedilla+AC10+XK_Ccedilla+NoSymbol: VKEY_OEM_1
+      {0x00E7, 0x2F, 0x00C7, 0x00DE,
+       0xC0},  // XK_ccedilla+AC10+XK_Ccedilla+XK_Thorn: VKEY_OEM_3
+      {0x00F6, 0x2F, 0x00D6, 0x0000,
+       0xC0},  // XK_odiaeresis+AC10+XK_Odiaeresis+NoSymbol: VKEY_OEM_3
+      {0x00F6, 0x2F, 0x00D6, 0x01DE,
+       0xC0},  // XK_odiaeresis+AC10+XK_Odiaeresis+XK_Tcedilla: VKEY_OEM_3
+      {0x00FC, 0x14, 0x00DC, 0x0000,
+       0xBF},  // XK_udiaeresis+AE11+XK_Udiaeresis+NoSymbol: VKEY_OEM_2
+      {0x00FC, 0x22, 0x00DC, 0x0000,
+       0xBA},  // XK_udiaeresis+AD11+XK_Udiaeresis+NoSymbol: VKEY_OEM_1
+      {0x00FC, 0x22, 0x00DC, 0x01A3,
+       0xC0},  // XK_udiaeresis+AD11+XK_Udiaeresis+XK_Lstroke: VKEY_OEM_3
+      {0x01EA, 0x3D, 0x01CA, 0x0000,
+       0xBD},  // XK_eogonek+AB10+XK_Eogonek+NoSymbol: VKEY_OEM_MINUS
+      {0x01EA, 0x3D, 0x01CA, 0x006E,
+       0xBF},  // XK_eogonek+AB10+XK_Eogonek+XK_n: VKEY_OEM_2
+      {0x03E7, 0x22, 0x03C7, 0x0000,
+       0xDB},  // XK_iogonek+AD11+XK_Iogonek+NoSymbol: VKEY_OEM_4
+      {0x03F9, 0x2F, 0x03D9, 0x0000,
+       0xC0},  // XK_uogonek+AC10+XK_Uogonek+NoSymbol: VKEY_OEM_3
+      {0x03F9, 0x2F, 0x03D9, 0x01DE,
+       0xBA},  // XK_uogonek+AC10+XK_Uogonek+XK_Tcedilla: VKEY_OEM_1
+};
+
+template <class T_MAP>
+KeyboardCode FindVK(const T_MAP& key, const T_MAP* map, size_t size) {
+  T_MAP comp = {0};
+  const T_MAP* p = std::lower_bound(map, map + size, key, comp);
+  if (p != map + size && !comp(*p, key) && !comp(key, *p))
+    return static_cast<KeyboardCode>(p->vk);
+  return VKEY_UNKNOWN;
+}
+
+}  // namespace
+
+// Get an ui::KeyboardCode from an X keyevent
+KeyboardCode KeyboardCodeFromXKeyEvent(const XEvent* xev) {
+  // Gets correct VKEY code from XEvent is performed as the following steps:
+  // 1. Gets the keysym without modifier states.
+  // 2. For [a-z] & [0-9] cases, returns the VKEY code accordingly.
+  // 3. Find keysym in map0.
+  // 4. If not found, fallback to find keysym + hardware_code in map1.
+  // 5. If not found, fallback to find keysym + keysym_shift + hardware_code
+  //    in map2.
+  // 6. If not found, fallback to find keysym + keysym_shift + keysym_altgr +
+  //    hardware_code in map3.
+  // 7. If not found, fallback to find in KeyboardCodeFromXKeysym(), which
+  //    mainly for non-letter keys.
+  // 8. If not found, fallback to find with the hardware code in US layout.
+
+  KeySym keysym = NoSymbol;
+  XEvent xkeyevent = {0};
+  if (xev->type == GenericEvent) {
+    // Convert the XI2 key event into a core key event so that we can
+    // continue to use XLookupString() until crbug.com/367732 is complete.
+    InitXKeyEventFromXIDeviceEvent(*xev, &xkeyevent);
+  } else {
+    xkeyevent.xkey = xev->xkey;
+  }
+  XKeyEvent* xkey = &xkeyevent.xkey;
+  xkey->state &= (~0xFF | Mod2Mask);  // Clears the xkey's state except numlock.
+  // XLookupKeysym does not take into consideration the state of the lock/shift
+  // etc. keys. So it is necessary to use XLookupString instead.
+  XLookupString(xkey, NULL, 0, &keysym, NULL);
+
+  // [a-z] cases.
+  if (keysym >= XK_a && keysym <= XK_z)
+    return static_cast<KeyboardCode>(VKEY_A + keysym - XK_a);
+
+  // [0-9] cases.
+  if (keysym >= XK_0 && keysym <= XK_9)
+    return static_cast<KeyboardCode>(VKEY_0 + keysym - XK_0);
+
+  KeyboardCode keycode = VKEY_UNKNOWN;
+
+  if (!IsKeypadKey(keysym) && !IsPrivateKeypadKey(keysym) &&
+      !IsCursorKey(keysym) && !IsPFKey(keysym) && !IsFunctionKey(keysym) &&
+      !IsModifierKey(keysym)) {
+    MAP0 key0 = {keysym & 0xFFFF, 0};
+    keycode = FindVK(key0, map0, arraysize(map0));
+    if (keycode != VKEY_UNKNOWN)
+      return keycode;
+
+    MAP1 key1 = {keysym & 0xFFFF, xkey->keycode, 0};
+    keycode = FindVK(key1, map1, arraysize(map1));
+    if (keycode != VKEY_UNKNOWN)
+      return keycode;
+
+    KeySym keysym_shift = NoSymbol;
+    xkey->state |= ShiftMask;
+    XLookupString(xkey, NULL, 0, &keysym_shift, NULL);
+    MAP2 key2 = {keysym & 0xFFFF, xkey->keycode, keysym_shift & 0xFFFF, 0};
+    keycode = FindVK(key2, map2, arraysize(map2));
+    if (keycode != VKEY_UNKNOWN)
+      return keycode;
+
+    KeySym keysym_altgr = NoSymbol;
+    xkey->state &= ~ShiftMask;
+    xkey->state |= Mod1Mask;
+    XLookupString(xkey, NULL, 0, &keysym_altgr, NULL);
+    MAP3 key3 = {keysym & 0xFFFF, xkey->keycode, keysym_shift & 0xFFFF,
+                 keysym_altgr & 0xFFFF, 0};
+    keycode = FindVK(key3, map3, arraysize(map3));
+    if (keycode != VKEY_UNKNOWN)
+      return keycode;
+
+    // On Linux some keys has AltGr char but not on Windows.
+    // So if cannot find VKEY with (ch0+sc+ch1+ch2) in map3, tries to fallback
+    // to just find VKEY with (ch0+sc+ch1). This is the best we could do.
+    MAP3 key4 = {keysym & 0xFFFF, xkey->keycode, keysym_shift & 0xFFFF, 0xFFFF,
+                 0};
+    const MAP3* p =
+        std::lower_bound(map3, map3 + arraysize(map3), key4, MAP3());
+    if (p != map3 + arraysize(map3) && p->ch0 == key4.ch0 && p->sc == key4.sc &&
+        p->ch1 == key4.ch1)
+      return static_cast<KeyboardCode>(p->vk);
+  }
+
+  keycode = KeyboardCodeFromXKeysym(keysym);
+  if (keycode == VKEY_UNKNOWN)
+    keycode = DefaultKeyboardCodeFromHardwareKeycode(xkey->keycode);
+
+  return keycode;
+}
+
+KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym) {
+  // TODO(sad): Have |keysym| go through the X map list?
+
+  switch (keysym) {
+    case XK_BackSpace:
+      return VKEY_BACK;
+    case XK_Delete:
+    case XK_KP_Delete:
+      return VKEY_DELETE;
+    case XK_Tab:
+    case XK_KP_Tab:
+    case XK_ISO_Left_Tab:
+    case XK_3270_BackTab:
+      return VKEY_TAB;
+    case XK_Linefeed:
+    case XK_Return:
+    case XK_KP_Enter:
+    case XK_ISO_Enter:
+      return VKEY_RETURN;
+    case XK_Clear:
+    case XK_KP_Begin:  // NumPad 5 without Num Lock, for crosbug.com/29169.
+      return VKEY_CLEAR;
+    case XK_KP_Space:
+    case XK_space:
+      return VKEY_SPACE;
+    case XK_Home:
+    case XK_KP_Home:
+      return VKEY_HOME;
+    case XK_End:
+    case XK_KP_End:
+      return VKEY_END;
+    case XK_Page_Up:
+    case XK_KP_Page_Up:  // aka XK_KP_Prior
+      return VKEY_PRIOR;
+    case XK_Page_Down:
+    case XK_KP_Page_Down:  // aka XK_KP_Next
+      return VKEY_NEXT;
+    case XK_Left:
+    case XK_KP_Left:
+      return VKEY_LEFT;
+    case XK_Right:
+    case XK_KP_Right:
+      return VKEY_RIGHT;
+    case XK_Down:
+    case XK_KP_Down:
+      return VKEY_DOWN;
+    case XK_Up:
+    case XK_KP_Up:
+      return VKEY_UP;
+    case XK_Escape:
+      return VKEY_ESCAPE;
+    case XK_Kana_Lock:
+    case XK_Kana_Shift:
+      return VKEY_KANA;
+    case XK_Hangul:
+      return VKEY_HANGUL;
+    case XK_Hangul_Hanja:
+      return VKEY_HANJA;
+    case XK_Kanji:
+      return VKEY_KANJI;
+    case XK_Henkan:
+      return VKEY_CONVERT;
+    case XK_Muhenkan:
+      return VKEY_NONCONVERT;
+    case XK_Zenkaku_Hankaku:
+      return VKEY_DBE_DBCSCHAR;
+
+    case XK_KP_0:
+    case XK_KP_1:
+    case XK_KP_2:
+    case XK_KP_3:
+    case XK_KP_4:
+    case XK_KP_5:
+    case XK_KP_6:
+    case XK_KP_7:
+    case XK_KP_8:
+    case XK_KP_9:
+      return static_cast<KeyboardCode>(VKEY_NUMPAD0 + (keysym - XK_KP_0));
+
+    case XK_multiply:
+    case XK_KP_Multiply:
+      return VKEY_MULTIPLY;
+    case XK_KP_Add:
+      return VKEY_ADD;
+    case XK_KP_Separator:
+      return VKEY_SEPARATOR;
+    case XK_KP_Subtract:
+      return VKEY_SUBTRACT;
+    case XK_KP_Decimal:
+      return VKEY_DECIMAL;
+    case XK_KP_Divide:
+      return VKEY_DIVIDE;
+    case XK_KP_Equal:
+    case XK_equal:
+    case XK_plus:
+      return VKEY_OEM_PLUS;
+    case XK_comma:
+    case XK_less:
+      return VKEY_OEM_COMMA;
+    case XK_minus:
+    case XK_underscore:
+      return VKEY_OEM_MINUS;
+    case XK_greater:
+    case XK_period:
+      return VKEY_OEM_PERIOD;
+    case XK_colon:
+    case XK_semicolon:
+      return VKEY_OEM_1;
+    case XK_question:
+    case XK_slash:
+      return VKEY_OEM_2;
+    case XK_asciitilde:
+    case XK_quoteleft:
+      return VKEY_OEM_3;
+    case XK_bracketleft:
+    case XK_braceleft:
+      return VKEY_OEM_4;
+    case XK_backslash:
+    case XK_bar:
+      return VKEY_OEM_5;
+    case XK_bracketright:
+    case XK_braceright:
+      return VKEY_OEM_6;
+    case XK_quoteright:
+    case XK_quotedbl:
+      return VKEY_OEM_7;
+    case XK_ISO_Level5_Shift:
+      return VKEY_OEM_8;
+    case XK_Shift_L:
+    case XK_Shift_R:
+      return VKEY_SHIFT;
+    case XK_Control_L:
+    case XK_Control_R:
+      return VKEY_CONTROL;
+    case XK_Meta_L:
+    case XK_Meta_R:
+    case XK_Alt_L:
+    case XK_Alt_R:
+      return VKEY_MENU;
+    case XK_ISO_Level3_Shift:
+    case XK_Mode_switch:
+      return VKEY_ALTGR;
+    case XK_Multi_key:
+      return VKEY_COMPOSE;
+    case XK_Pause:
+      return VKEY_PAUSE;
+    case XK_Caps_Lock:
+      return VKEY_CAPITAL;
+    case XK_Num_Lock:
+      return VKEY_NUMLOCK;
+    case XK_Scroll_Lock:
+      return VKEY_SCROLL;
+    case XK_Select:
+      return VKEY_SELECT;
+    case XK_Print:
+      return VKEY_PRINT;
+    case XK_Execute:
+      return VKEY_EXECUTE;
+    case XK_Insert:
+    case XK_KP_Insert:
+      return VKEY_INSERT;
+    case XK_Help:
+      return VKEY_HELP;
+    case XK_Super_L:
+      return VKEY_LWIN;
+    case XK_Super_R:
+      return VKEY_RWIN;
+    case XK_Menu:
+      return VKEY_APPS;
+    case XK_F1:
+    case XK_F2:
+    case XK_F3:
+    case XK_F4:
+    case XK_F5:
+    case XK_F6:
+    case XK_F7:
+    case XK_F8:
+    case XK_F9:
+    case XK_F10:
+    case XK_F11:
+    case XK_F12:
+    case XK_F13:
+    case XK_F14:
+    case XK_F15:
+    case XK_F16:
+    case XK_F17:
+    case XK_F18:
+    case XK_F19:
+    case XK_F20:
+    case XK_F21:
+    case XK_F22:
+    case XK_F23:
+    case XK_F24:
+      return static_cast<KeyboardCode>(VKEY_F1 + (keysym - XK_F1));
+    case XK_KP_F1:
+    case XK_KP_F2:
+    case XK_KP_F3:
+    case XK_KP_F4:
+      return static_cast<KeyboardCode>(VKEY_F1 + (keysym - XK_KP_F1));
+
+    case XK_guillemotleft:
+    case XK_guillemotright:
+    case XK_degree:
+    // In the case of canadian multilingual keyboard layout, VKEY_OEM_102 is
+    // assigned to ugrave key.
+    case XK_ugrave:
+    case XK_Ugrave:
+    case XK_brokenbar:
+      return VKEY_OEM_102;  // international backslash key in 102 keyboard.
+
+    // When evdev is in use, /usr/share/X11/xkb/symbols/inet maps F13-18 keys
+    // to the special XF86XK symbols to support Microsoft Ergonomic keyboards:
+    // https://bugs.freedesktop.org/show_bug.cgi?id=5783
+    // In Chrome, we map these X key symbols back to F13-18 since we don't have
+    // VKEYs for these XF86XK symbols.
+    case XF86XK_Launch5:
+      return VKEY_F14;
+    case XF86XK_Launch6:
+      return VKEY_F15;
+    case XF86XK_Launch7:
+      return VKEY_F16;
+    case XF86XK_Launch8:
+      return VKEY_F17;
+    case XF86XK_Launch9:
+      return VKEY_F18;
+
+    // For supporting multimedia buttons on a USB keyboard.
+    case XF86XK_Back:
+      return VKEY_BROWSER_BACK;
+    case XF86XK_Forward:
+      return VKEY_BROWSER_FORWARD;
+    case XF86XK_Reload:
+      return VKEY_BROWSER_REFRESH;
+    case XF86XK_Stop:
+      return VKEY_BROWSER_STOP;
+    case XF86XK_Search:
+      return VKEY_BROWSER_SEARCH;
+    case XF86XK_Favorites:
+      return VKEY_BROWSER_FAVORITES;
+    case XF86XK_HomePage:
+      return VKEY_BROWSER_HOME;
+    case XF86XK_AudioMute:
+      return VKEY_VOLUME_MUTE;
+    case XF86XK_AudioLowerVolume:
+      return VKEY_VOLUME_DOWN;
+    case XF86XK_AudioRaiseVolume:
+      return VKEY_VOLUME_UP;
+    case XF86XK_AudioNext:
+      return VKEY_MEDIA_NEXT_TRACK;
+    case XF86XK_AudioPrev:
+      return VKEY_MEDIA_PREV_TRACK;
+    case XF86XK_AudioStop:
+      return VKEY_MEDIA_STOP;
+    case XF86XK_AudioPlay:
+      return VKEY_MEDIA_PLAY_PAUSE;
+    case XF86XK_Mail:
+      return VKEY_MEDIA_LAUNCH_MAIL;
+    case XF86XK_LaunchA:  // F3 on an Apple keyboard.
+      return VKEY_MEDIA_LAUNCH_APP1;
+    case XF86XK_LaunchB:  // F4 on an Apple keyboard.
+    case XF86XK_Calculator:
+      return VKEY_MEDIA_LAUNCH_APP2;
+
+    // XF86XK_Tools is generated from HID Usage AL_CONSUMER_CONTROL_CONFIG
+    // (Usage 0x0183, Page 0x0C) and most commonly launches the OS default
+    // media player (see crbug.com/398345).
+    case XF86XK_Tools:
+      return VKEY_MEDIA_LAUNCH_MEDIA_SELECT;
+
+    case XF86XK_WLAN:
+      return VKEY_WLAN;
+    case XF86XK_PowerOff:
+      return VKEY_POWER;
+    case XF86XK_MonBrightnessDown:
+      return VKEY_BRIGHTNESS_DOWN;
+    case XF86XK_MonBrightnessUp:
+      return VKEY_BRIGHTNESS_UP;
+    case XF86XK_KbdBrightnessDown:
+      return VKEY_KBD_BRIGHTNESS_DOWN;
+    case XF86XK_KbdBrightnessUp:
+      return VKEY_KBD_BRIGHTNESS_UP;
+
+    // TODO(sad): some keycodes are still missing.
+  }
+  DVLOG(1) << "Unknown keysym: " << base::StringPrintf("0x%x", keysym);
+  return VKEY_UNKNOWN;
+}
+
+const char* CodeFromXEvent(const XEvent* xev) {
+  int keycode = (xev->type == GenericEvent)
+                    ? static_cast<XIDeviceEvent*>(xev->xcookie.data)->detail
+                    : xev->xkey.keycode;
+  return ui::KeycodeConverter::NativeKeycodeToCode(keycode);
+}
+
+uint16 GetCharacterFromXEvent(const XEvent* xev) {
+  XEvent xkeyevent = {0};
+  const XKeyEvent* xkey = NULL;
+  if (xev->type == GenericEvent) {
+    // Convert the XI2 key event into a core key event so that we can
+    // continue to use XLookupString() until crbug.com/367732 is complete.
+    InitXKeyEventFromXIDeviceEvent(*xev, &xkeyevent);
+    xkey = &xkeyevent.xkey;
+  } else {
+    xkey = &xev->xkey;
+  }
+  KeySym keysym = XK_VoidSymbol;
+  XLookupString(const_cast<XKeyEvent*>(xkey), NULL, 0, &keysym, NULL);
+  return GetUnicodeCharacterFromXKeySym(keysym);
+}
+
+KeyboardCode DefaultKeyboardCodeFromHardwareKeycode(
+    unsigned int hardware_code) {
+  // This function assumes that X11 is using evdev-based keycodes.
+  static const KeyboardCode kHardwareKeycodeMap[] = {
+      // Please refer to below links for the table content:
+      // http://www.w3.org/TR/DOM-Level-3-Events-code/#keyboard-101
+      // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode
+      // http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
+      VKEY_UNKNOWN,       // 0x00:
+      VKEY_UNKNOWN,       // 0x01:
+      VKEY_UNKNOWN,       // 0x02:
+      VKEY_UNKNOWN,       // 0x03:
+      VKEY_UNKNOWN,       // 0x04:
+      VKEY_UNKNOWN,       // 0x05:
+      VKEY_UNKNOWN,       // 0x06:
+      VKEY_UNKNOWN,       // XKB   evdev (XKB - 8)      X KeySym
+      VKEY_UNKNOWN,       // ===   ===============      ======
+      VKEY_ESCAPE,        // 0x09: KEY_ESC              Escape
+      VKEY_1,             // 0x0A: KEY_1                1
+      VKEY_2,             // 0x0B: KEY_2                2
+      VKEY_3,             // 0x0C: KEY_3                3
+      VKEY_4,             // 0x0D: KEY_4                4
+      VKEY_5,             // 0x0E: KEY_5                5
+      VKEY_6,             // 0x0F: KEY_6                6
+      VKEY_7,             // 0x10: KEY_7                7
+      VKEY_8,             // 0x11: KEY_8                8
+      VKEY_9,             // 0x12: KEY_9                9
+      VKEY_0,             // 0x13: KEY_0                0
+      VKEY_OEM_MINUS,     // 0x14: KEY_MINUS            minus
+      VKEY_OEM_PLUS,      // 0x15: KEY_EQUAL            equal
+      VKEY_BACK,          // 0x16: KEY_BACKSPACE        BackSpace
+      VKEY_TAB,           // 0x17: KEY_TAB              Tab
+      VKEY_Q,             // 0x18: KEY_Q                q
+      VKEY_W,             // 0x19: KEY_W                w
+      VKEY_E,             // 0x1A: KEY_E                e
+      VKEY_R,             // 0x1B: KEY_R                r
+      VKEY_T,             // 0x1C: KEY_T                t
+      VKEY_Y,             // 0x1D: KEY_Y                y
+      VKEY_U,             // 0x1E: KEY_U                u
+      VKEY_I,             // 0x1F: KEY_I                i
+      VKEY_O,             // 0x20: KEY_O                o
+      VKEY_P,             // 0x21: KEY_P                p
+      VKEY_OEM_4,         // 0x22: KEY_LEFTBRACE        bracketleft
+      VKEY_OEM_6,         // 0x23: KEY_RIGHTBRACE       bracketright
+      VKEY_RETURN,        // 0x24: KEY_ENTER            Return
+      VKEY_LCONTROL,      // 0x25: KEY_LEFTCTRL         Control_L
+      VKEY_A,             // 0x26: KEY_A                a
+      VKEY_S,             // 0x27: KEY_S                s
+      VKEY_D,             // 0x28: KEY_D                d
+      VKEY_F,             // 0x29: KEY_F                f
+      VKEY_G,             // 0x2A: KEY_G                g
+      VKEY_H,             // 0x2B: KEY_H                h
+      VKEY_J,             // 0x2C: KEY_J                j
+      VKEY_K,             // 0x2D: KEY_K                k
+      VKEY_L,             // 0x2E: KEY_L                l
+      VKEY_OEM_1,         // 0x2F: KEY_SEMICOLON        semicolon
+      VKEY_OEM_7,         // 0x30: KEY_APOSTROPHE       apostrophe
+      VKEY_OEM_3,         // 0x31: KEY_GRAVE            grave
+      VKEY_LSHIFT,        // 0x32: KEY_LEFTSHIFT        Shift_L
+      VKEY_OEM_5,         // 0x33: KEY_BACKSLASH        backslash
+      VKEY_Z,             // 0x34: KEY_Z                z
+      VKEY_X,             // 0x35: KEY_X                x
+      VKEY_C,             // 0x36: KEY_C                c
+      VKEY_V,             // 0x37: KEY_V                v
+      VKEY_B,             // 0x38: KEY_B                b
+      VKEY_N,             // 0x39: KEY_N                n
+      VKEY_M,             // 0x3A: KEY_M                m
+      VKEY_OEM_COMMA,     // 0x3B: KEY_COMMA            comma
+      VKEY_OEM_PERIOD,    // 0x3C: KEY_DOT              period
+      VKEY_OEM_2,         // 0x3D: KEY_SLASH            slash
+      VKEY_RSHIFT,        // 0x3E: KEY_RIGHTSHIFT       Shift_R
+      VKEY_MULTIPLY,      // 0x3F: KEY_KPASTERISK       KP_Multiply
+      VKEY_LMENU,         // 0x40: KEY_LEFTALT          Alt_L
+      VKEY_SPACE,         // 0x41: KEY_SPACE            space
+      VKEY_CAPITAL,       // 0x42: KEY_CAPSLOCK         Caps_Lock
+      VKEY_F1,            // 0x43: KEY_F1               F1
+      VKEY_F2,            // 0x44: KEY_F2               F2
+      VKEY_F3,            // 0x45: KEY_F3               F3
+      VKEY_F4,            // 0x46: KEY_F4               F4
+      VKEY_F5,            // 0x47: KEY_F5               F5
+      VKEY_F6,            // 0x48: KEY_F6               F6
+      VKEY_F7,            // 0x49: KEY_F7               F7
+      VKEY_F8,            // 0x4A: KEY_F8               F8
+      VKEY_F9,            // 0x4B: KEY_F9               F9
+      VKEY_F10,           // 0x4C: KEY_F10              F10
+      VKEY_NUMLOCK,       // 0x4D: KEY_NUMLOCK          Num_Lock
+      VKEY_SCROLL,        // 0x4E: KEY_SCROLLLOCK       Scroll_Lock
+      VKEY_NUMPAD7,       // 0x4F: KEY_KP7              KP_7
+      VKEY_NUMPAD8,       // 0x50: KEY_KP8              KP_8
+      VKEY_NUMPAD9,       // 0x51: KEY_KP9              KP_9
+      VKEY_SUBTRACT,      // 0x52: KEY_KPMINUS          KP_Subtract
+      VKEY_NUMPAD4,       // 0x53: KEY_KP4              KP_4
+      VKEY_NUMPAD5,       // 0x54: KEY_KP5              KP_5
+      VKEY_NUMPAD6,       // 0x55: KEY_KP6              KP_6
+      VKEY_ADD,           // 0x56: KEY_KPPLUS           KP_Add
+      VKEY_NUMPAD1,       // 0x57: KEY_KP1              KP_1
+      VKEY_NUMPAD2,       // 0x58: KEY_KP2              KP_2
+      VKEY_NUMPAD3,       // 0x59: KEY_KP3              KP_3
+      VKEY_NUMPAD0,       // 0x5A: KEY_KP0              KP_0
+      VKEY_DECIMAL,       // 0x5B: KEY_KPDOT            KP_Decimal
+      VKEY_UNKNOWN,       // 0x5C:
+      VKEY_DBE_DBCSCHAR,  // 0x5D: KEY_ZENKAKUHANKAKU   Zenkaku_Hankaku
+      VKEY_OEM_5,         // 0x5E: KEY_102ND            backslash
+      VKEY_F11,           // 0x5F: KEY_F11              F11
+      VKEY_F12,           // 0x60: KEY_F12              F12
+      VKEY_OEM_102,       // 0x61: KEY_RO               Romaji
+      VKEY_UNSUPPORTED,   // 0x62: KEY_KATAKANA         Katakana
+      VKEY_UNSUPPORTED,   // 0x63: KEY_HIRAGANA         Hiragana
+      VKEY_CONVERT,       // 0x64: KEY_HENKAN           Henkan
+      VKEY_UNSUPPORTED,   // 0x65: KEY_KATAKANAHIRAGANA Hiragana_Katakana
+      VKEY_NONCONVERT,    // 0x66: KEY_MUHENKAN         Muhenkan
+      VKEY_SEPARATOR,     // 0x67: KEY_KPJPCOMMA        KP_Separator
+      VKEY_RETURN,        // 0x68: KEY_KPENTER          KP_Enter
+      VKEY_RCONTROL,      // 0x69: KEY_RIGHTCTRL        Control_R
+      VKEY_DIVIDE,        // 0x6A: KEY_KPSLASH          KP_Divide
+      VKEY_PRINT,         // 0x6B: KEY_SYSRQ            Print
+      VKEY_RMENU,         // 0x6C: KEY_RIGHTALT         Alt_R
+      VKEY_RETURN,        // 0x6D: KEY_LINEFEED         Linefeed
+      VKEY_HOME,          // 0x6E: KEY_HOME             Home
+      VKEY_UP,            // 0x6F: KEY_UP               Up
+      VKEY_PRIOR,         // 0x70: KEY_PAGEUP           Page_Up
+      VKEY_LEFT,          // 0x71: KEY_LEFT             Left
+      VKEY_RIGHT,         // 0x72: KEY_RIGHT            Right
+      VKEY_END,           // 0x73: KEY_END              End
+      VKEY_DOWN,          // 0x74: KEY_DOWN             Down
+      VKEY_NEXT,          // 0x75: KEY_PAGEDOWN         Page_Down
+      VKEY_INSERT,        // 0x76: KEY_INSERT           Insert
+      VKEY_DELETE,        // 0x77: KEY_DELETE           Delete
+      VKEY_UNSUPPORTED,   // 0x78: KEY_MACRO
+      VKEY_VOLUME_MUTE,   // 0x79: KEY_MUTE             XF86AudioMute
+      VKEY_VOLUME_DOWN,   // 0x7A: KEY_VOLUMEDOWN       XF86AudioLowerVolume
+      VKEY_VOLUME_UP,     // 0x7B: KEY_VOLUMEUP         XF86AudioRaiseVolume
+      VKEY_POWER,         // 0x7C: KEY_POWER            XF86PowerOff
+      VKEY_OEM_PLUS,      // 0x7D: KEY_KPEQUAL          KP_Equal
+      VKEY_UNSUPPORTED,   // 0x7E: KEY_KPPLUSMINUS      plusminus
+      VKEY_PAUSE,         // 0x7F: KEY_PAUSE            Pause
+      VKEY_MEDIA_LAUNCH_APP1,  // 0x80: KEY_SCALE            XF86LaunchA
+      VKEY_DECIMAL,            // 0x81: KEY_KPCOMMA          KP_Decimal
+      VKEY_HANGUL,             // 0x82: KEY_HANGUEL          Hangul
+      VKEY_HANJA,              // 0x83: KEY_HANJA            Hangul_Hanja
+      VKEY_OEM_5,              // 0x84: KEY_YEN              yen
+      VKEY_LWIN,               // 0x85: KEY_LEFTMETA         Super_L
+      VKEY_RWIN,               // 0x86: KEY_RIGHTMETA        Super_R
+      VKEY_COMPOSE,            // 0x87: KEY_COMPOSE          Menu
+  };
+
+  if (hardware_code >= arraysize(kHardwareKeycodeMap)) {
+    // Additional keycodes used by the Chrome OS top row special function keys.
+    switch (hardware_code) {
+      case 0xA6:  // KEY_BACK
+        return VKEY_BACK;
+      case 0xA7:  // KEY_FORWARD
+        return VKEY_BROWSER_FORWARD;
+      case 0xB5:  // KEY_REFRESH
+        return VKEY_BROWSER_REFRESH;
+      case 0xD4:  // KEY_DASHBOARD
+        return VKEY_MEDIA_LAUNCH_APP2;
+      case 0xE8:  // KEY_BRIGHTNESSDOWN
+        return VKEY_BRIGHTNESS_DOWN;
+      case 0xE9:  // KEY_BRIGHTNESSUP
+        return VKEY_BRIGHTNESS_UP;
+    }
+    return VKEY_UNKNOWN;
+  }
+  return kHardwareKeycodeMap[hardware_code];
+}
+
+// TODO(jcampan): this method might be incomplete.
+int XKeysymForWindowsKeyCode(KeyboardCode keycode, bool shift) {
+  switch (keycode) {
+    case VKEY_NUMPAD0:
+      return XK_KP_0;
+    case VKEY_NUMPAD1:
+      return XK_KP_1;
+    case VKEY_NUMPAD2:
+      return XK_KP_2;
+    case VKEY_NUMPAD3:
+      return XK_KP_3;
+    case VKEY_NUMPAD4:
+      return XK_KP_4;
+    case VKEY_NUMPAD5:
+      return XK_KP_5;
+    case VKEY_NUMPAD6:
+      return XK_KP_6;
+    case VKEY_NUMPAD7:
+      return XK_KP_7;
+    case VKEY_NUMPAD8:
+      return XK_KP_8;
+    case VKEY_NUMPAD9:
+      return XK_KP_9;
+    case VKEY_MULTIPLY:
+      return XK_KP_Multiply;
+    case VKEY_ADD:
+      return XK_KP_Add;
+    case VKEY_SUBTRACT:
+      return XK_KP_Subtract;
+    case VKEY_DECIMAL:
+      return XK_KP_Decimal;
+    case VKEY_DIVIDE:
+      return XK_KP_Divide;
+
+    case VKEY_BACK:
+      return XK_BackSpace;
+    case VKEY_TAB:
+      return shift ? XK_ISO_Left_Tab : XK_Tab;
+    case VKEY_CLEAR:
+      return XK_Clear;
+    case VKEY_RETURN:
+      return XK_Return;
+    case VKEY_SHIFT:
+      return XK_Shift_L;
+    case VKEY_CONTROL:
+      return XK_Control_L;
+    case VKEY_MENU:
+      return XK_Alt_L;
+    case VKEY_APPS:
+      return XK_Menu;
+    case VKEY_ALTGR:
+      return XK_ISO_Level3_Shift;
+    case VKEY_COMPOSE:
+      return XK_Multi_key;
+
+    case VKEY_PAUSE:
+      return XK_Pause;
+    case VKEY_CAPITAL:
+      return XK_Caps_Lock;
+    case VKEY_KANA:
+      return XK_Kana_Lock;
+    case VKEY_HANJA:
+      return XK_Hangul_Hanja;
+    case VKEY_CONVERT:
+      return XK_Henkan;
+    case VKEY_NONCONVERT:
+      return XK_Muhenkan;
+    case VKEY_DBE_SBCSCHAR:
+      return XK_Zenkaku_Hankaku;
+    case VKEY_DBE_DBCSCHAR:
+      return XK_Zenkaku_Hankaku;
+    case VKEY_ESCAPE:
+      return XK_Escape;
+    case VKEY_SPACE:
+      return XK_space;
+    case VKEY_PRIOR:
+      return XK_Page_Up;
+    case VKEY_NEXT:
+      return XK_Page_Down;
+    case VKEY_END:
+      return XK_End;
+    case VKEY_HOME:
+      return XK_Home;
+    case VKEY_LEFT:
+      return XK_Left;
+    case VKEY_UP:
+      return XK_Up;
+    case VKEY_RIGHT:
+      return XK_Right;
+    case VKEY_DOWN:
+      return XK_Down;
+    case VKEY_SELECT:
+      return XK_Select;
+    case VKEY_PRINT:
+      return XK_Print;
+    case VKEY_EXECUTE:
+      return XK_Execute;
+    case VKEY_INSERT:
+      return XK_Insert;
+    case VKEY_DELETE:
+      return XK_Delete;
+    case VKEY_HELP:
+      return XK_Help;
+    case VKEY_0:
+      return shift ? XK_parenright : XK_0;
+    case VKEY_1:
+      return shift ? XK_exclam : XK_1;
+    case VKEY_2:
+      return shift ? XK_at : XK_2;
+    case VKEY_3:
+      return shift ? XK_numbersign : XK_3;
+    case VKEY_4:
+      return shift ? XK_dollar : XK_4;
+    case VKEY_5:
+      return shift ? XK_percent : XK_5;
+    case VKEY_6:
+      return shift ? XK_asciicircum : XK_6;
+    case VKEY_7:
+      return shift ? XK_ampersand : XK_7;
+    case VKEY_8:
+      return shift ? XK_asterisk : XK_8;
+    case VKEY_9:
+      return shift ? XK_parenleft : XK_9;
+
+    case VKEY_A:
+    case VKEY_B:
+    case VKEY_C:
+    case VKEY_D:
+    case VKEY_E:
+    case VKEY_F:
+    case VKEY_G:
+    case VKEY_H:
+    case VKEY_I:
+    case VKEY_J:
+    case VKEY_K:
+    case VKEY_L:
+    case VKEY_M:
+    case VKEY_N:
+    case VKEY_O:
+    case VKEY_P:
+    case VKEY_Q:
+    case VKEY_R:
+    case VKEY_S:
+    case VKEY_T:
+    case VKEY_U:
+    case VKEY_V:
+    case VKEY_W:
+    case VKEY_X:
+    case VKEY_Y:
+    case VKEY_Z:
+      return (shift ? XK_A : XK_a) + (keycode - VKEY_A);
+
+    case VKEY_LWIN:
+      return XK_Super_L;
+    case VKEY_RWIN:
+      return XK_Super_R;
+
+    case VKEY_NUMLOCK:
+      return XK_Num_Lock;
+
+    case VKEY_SCROLL:
+      return XK_Scroll_Lock;
+
+    case VKEY_OEM_1:
+      return shift ? XK_colon : XK_semicolon;
+    case VKEY_OEM_PLUS:
+      return shift ? XK_plus : XK_equal;
+    case VKEY_OEM_COMMA:
+      return shift ? XK_less : XK_comma;
+    case VKEY_OEM_MINUS:
+      return shift ? XK_underscore : XK_minus;
+    case VKEY_OEM_PERIOD:
+      return shift ? XK_greater : XK_period;
+    case VKEY_OEM_2:
+      return shift ? XK_question : XK_slash;
+    case VKEY_OEM_3:
+      return shift ? XK_asciitilde : XK_quoteleft;
+    case VKEY_OEM_4:
+      return shift ? XK_braceleft : XK_bracketleft;
+    case VKEY_OEM_5:
+      return shift ? XK_bar : XK_backslash;
+    case VKEY_OEM_6:
+      return shift ? XK_braceright : XK_bracketright;
+    case VKEY_OEM_7:
+      return shift ? XK_quotedbl : XK_quoteright;
+    case VKEY_OEM_8:
+      return XK_ISO_Level5_Shift;
+    case VKEY_OEM_102:
+      return shift ? XK_guillemotleft : XK_guillemotright;
+
+    case VKEY_F1:
+    case VKEY_F2:
+    case VKEY_F3:
+    case VKEY_F4:
+    case VKEY_F5:
+    case VKEY_F6:
+    case VKEY_F7:
+    case VKEY_F8:
+    case VKEY_F9:
+    case VKEY_F10:
+    case VKEY_F11:
+    case VKEY_F12:
+    case VKEY_F13:
+    case VKEY_F14:
+    case VKEY_F15:
+    case VKEY_F16:
+    case VKEY_F17:
+    case VKEY_F18:
+    case VKEY_F19:
+    case VKEY_F20:
+    case VKEY_F21:
+    case VKEY_F22:
+    case VKEY_F23:
+    case VKEY_F24:
+      return XK_F1 + (keycode - VKEY_F1);
+
+    case VKEY_BROWSER_BACK:
+      return XF86XK_Back;
+    case VKEY_BROWSER_FORWARD:
+      return XF86XK_Forward;
+    case VKEY_BROWSER_REFRESH:
+      return XF86XK_Reload;
+    case VKEY_BROWSER_STOP:
+      return XF86XK_Stop;
+    case VKEY_BROWSER_SEARCH:
+      return XF86XK_Search;
+    case VKEY_BROWSER_FAVORITES:
+      return XF86XK_Favorites;
+    case VKEY_BROWSER_HOME:
+      return XF86XK_HomePage;
+    case VKEY_VOLUME_MUTE:
+      return XF86XK_AudioMute;
+    case VKEY_VOLUME_DOWN:
+      return XF86XK_AudioLowerVolume;
+    case VKEY_VOLUME_UP:
+      return XF86XK_AudioRaiseVolume;
+    case VKEY_MEDIA_NEXT_TRACK:
+      return XF86XK_AudioNext;
+    case VKEY_MEDIA_PREV_TRACK:
+      return XF86XK_AudioPrev;
+    case VKEY_MEDIA_STOP:
+      return XF86XK_AudioStop;
+    case VKEY_MEDIA_PLAY_PAUSE:
+      return XF86XK_AudioPlay;
+    case VKEY_MEDIA_LAUNCH_MAIL:
+      return XF86XK_Mail;
+    case VKEY_MEDIA_LAUNCH_APP1:
+      return XF86XK_LaunchA;
+    case VKEY_MEDIA_LAUNCH_APP2:
+      return XF86XK_LaunchB;
+    case VKEY_WLAN:
+      return XF86XK_WLAN;
+    case VKEY_POWER:
+      return XF86XK_PowerOff;
+    case VKEY_BRIGHTNESS_DOWN:
+      return XF86XK_MonBrightnessDown;
+    case VKEY_BRIGHTNESS_UP:
+      return XF86XK_MonBrightnessUp;
+    case VKEY_KBD_BRIGHTNESS_DOWN:
+      return XF86XK_KbdBrightnessDown;
+    case VKEY_KBD_BRIGHTNESS_UP:
+      return XF86XK_KbdBrightnessUp;
+
+    default:
+      LOG(WARNING) << "Unknown keycode:" << keycode;
+      return 0;
+    }
+}
+
+void InitXKeyEventFromXIDeviceEvent(const XEvent& src, XEvent* xkeyevent) {
+  DCHECK(src.type == GenericEvent);
+  XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(src.xcookie.data);
+  switch (xievent->evtype) {
+    case XI_KeyPress:
+      xkeyevent->type = KeyPress;
+      break;
+    case XI_KeyRelease:
+      xkeyevent->type = KeyRelease;
+      break;
+    default:
+      NOTREACHED();
+  }
+  xkeyevent->xkey.serial = xievent->serial;
+  xkeyevent->xkey.send_event = xievent->send_event;
+  xkeyevent->xkey.display = xievent->display;
+  xkeyevent->xkey.window = xievent->event;
+  xkeyevent->xkey.root = xievent->root;
+  xkeyevent->xkey.subwindow = xievent->child;
+  xkeyevent->xkey.time = xievent->time;
+  xkeyevent->xkey.x = xievent->event_x;
+  xkeyevent->xkey.y = xievent->event_y;
+  xkeyevent->xkey.x_root = xievent->root_x;
+  xkeyevent->xkey.y_root = xievent->root_y;
+  xkeyevent->xkey.state = xievent->mods.effective;
+  xkeyevent->xkey.keycode = xievent->detail;
+  xkeyevent->xkey.same_screen = 1;
+}
+
+unsigned int XKeyCodeForWindowsKeyCode(ui::KeyboardCode key_code,
+                                       int flags,
+                                       XDisplay* display) {
+  // SHIFT state is ignored in the call to XKeysymForWindowsKeyCode() here
+  // because we map the XKeysym back to a keycode, i.e. a physical key position.
+  // Using a SHIFT-modified XKeysym would sometimes yield X keycodes that,
+  // while technically valid, may be surprising in that they do not match
+  // the keycode of the original press, and conflict with assumptions in
+  // other code.
+  //
+  // For example, in a US layout, Shift-9 has the interpretation XK_parenleft,
+  // but the keycode KEY_9 alone does not map to XK_parenleft; instead,
+  // XKeysymToKeycode() returns KEY_KPLEFTPAREN (keypad left parenthesis)
+  // which does map to XK_parenleft -- notwithstanding that keyboards with
+  // dedicated number pad parenthesis keys are currently uncommon.
+  //
+  // Similarly, Shift-Comma has the interpretation XK_less, but KEY_COMMA
+  // alone does not map to XK_less; XKeysymToKeycode() returns KEY_102ND
+  // (the '<>' key between Shift and Z on 105-key keyboards) which does.
+  //
+  // crbug.com/386066 and crbug.com/390263 are examples of problems
+  // associated with this.
+  //
+  return XKeysymToKeycode(display, XKeysymForWindowsKeyCode(key_code, false));
+}
+
+}  // namespace ui
diff --git a/ui/events/keycodes/keyboard_code_conversion_x.h b/ui/events/keycodes/keyboard_code_conversion_x.h
new file mode 100644
index 0000000..1626691
--- /dev/null
+++ b/ui/events/keycodes/keyboard_code_conversion_x.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2011 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 UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_X_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_X_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+typedef union _XEvent XEvent;
+typedef struct _XDisplay XDisplay;
+
+namespace ui {
+
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromXKeyEvent(const XEvent* xev);
+
+EVENTS_BASE_EXPORT KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym);
+
+EVENTS_BASE_EXPORT const char* CodeFromXEvent(const XEvent* xev);
+
+// Returns a character on a standard US PC keyboard from an XEvent.
+EVENTS_BASE_EXPORT uint16 GetCharacterFromXEvent(const XEvent* xev);
+
+// Converts a KeyboardCode into an X KeySym.
+EVENTS_BASE_EXPORT int XKeysymForWindowsKeyCode(KeyboardCode keycode,
+                                                bool shift);
+
+// Returns a XKeyEvent keycode (scancode) for a KeyboardCode. Keyboard layouts
+// are usually not injective, so inverse mapping should be avoided when
+// practical. A round-trip keycode -> KeyboardCode -> keycode will not
+// necessarily return the original keycode.
+EVENTS_BASE_EXPORT unsigned int XKeyCodeForWindowsKeyCode(KeyboardCode key_code,
+                                                          int flags,
+                                                          XDisplay* display);
+
+// Converts an X keycode into ui::KeyboardCode.
+EVENTS_BASE_EXPORT KeyboardCode
+    DefaultKeyboardCodeFromHardwareKeycode(unsigned int hardware_code);
+
+// Initializes a core XKeyEvent from an XI2 key event.
+EVENTS_BASE_EXPORT void InitXKeyEventFromXIDeviceEvent(const XEvent& src,
+                                                       XEvent* dst);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_X_H_
diff --git a/ui/events/keycodes/keyboard_codes.h b/ui/events/keycodes/keyboard_codes.h
new file mode 100644
index 0000000..00a7847
--- /dev/null
+++ b/ui/events/keycodes/keyboard_codes.h
@@ -0,0 +1,16 @@
+// Copyright (c) 2011 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 UI_EVENTS_KEYCODES_KEYBOARD_CODES_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODES_H_
+
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "ui/events/keycodes/keyboard_codes_win.h"
+#elif defined(OS_POSIX)
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#endif
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODES_H_
diff --git a/ui/events/keycodes/keyboard_codes_posix.h b/ui/events/keycodes/keyboard_codes_posix.h
new file mode 100644
index 0000000..e984839
--- /dev/null
+++ b/ui/events/keycodes/keyboard_codes_posix.h
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 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.
+
+/*
+ * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com. All rights reserved.
+ * Copyright (C) 2008, 2009 Google Inc.
+ *
+ * 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 COMPUTER, INC. ``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 COMPUTER, INC. OR
+ * 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 UI_EVENTS_KEYCODES_KEYBOARD_CODES_POSIX_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODES_POSIX_H_
+
+namespace ui {
+
+enum KeyboardCode {
+  VKEY_BACK = 0x08,
+  VKEY_TAB = 0x09,
+  VKEY_BACKTAB = 0x0A,
+  VKEY_CLEAR = 0x0C,
+  VKEY_RETURN = 0x0D,
+  VKEY_SHIFT = 0x10,
+  VKEY_CONTROL = 0x11,
+  VKEY_MENU = 0x12,
+  VKEY_PAUSE = 0x13,
+  VKEY_CAPITAL = 0x14,
+  VKEY_KANA = 0x15,
+  VKEY_HANGUL = 0x15,
+  VKEY_JUNJA = 0x17,
+  VKEY_FINAL = 0x18,
+  VKEY_HANJA = 0x19,
+  VKEY_KANJI = 0x19,
+  VKEY_ESCAPE = 0x1B,
+  VKEY_CONVERT = 0x1C,
+  VKEY_NONCONVERT = 0x1D,
+  VKEY_ACCEPT = 0x1E,
+  VKEY_MODECHANGE = 0x1F,
+  VKEY_SPACE = 0x20,
+  VKEY_PRIOR = 0x21,
+  VKEY_NEXT = 0x22,
+  VKEY_END = 0x23,
+  VKEY_HOME = 0x24,
+  VKEY_LEFT = 0x25,
+  VKEY_UP = 0x26,
+  VKEY_RIGHT = 0x27,
+  VKEY_DOWN = 0x28,
+  VKEY_SELECT = 0x29,
+  VKEY_PRINT = 0x2A,
+  VKEY_EXECUTE = 0x2B,
+  VKEY_SNAPSHOT = 0x2C,
+  VKEY_INSERT = 0x2D,
+  VKEY_DELETE = 0x2E,
+  VKEY_HELP = 0x2F,
+  VKEY_0 = 0x30,
+  VKEY_1 = 0x31,
+  VKEY_2 = 0x32,
+  VKEY_3 = 0x33,
+  VKEY_4 = 0x34,
+  VKEY_5 = 0x35,
+  VKEY_6 = 0x36,
+  VKEY_7 = 0x37,
+  VKEY_8 = 0x38,
+  VKEY_9 = 0x39,
+  VKEY_A = 0x41,
+  VKEY_B = 0x42,
+  VKEY_C = 0x43,
+  VKEY_D = 0x44,
+  VKEY_E = 0x45,
+  VKEY_F = 0x46,
+  VKEY_G = 0x47,
+  VKEY_H = 0x48,
+  VKEY_I = 0x49,
+  VKEY_J = 0x4A,
+  VKEY_K = 0x4B,
+  VKEY_L = 0x4C,
+  VKEY_M = 0x4D,
+  VKEY_N = 0x4E,
+  VKEY_O = 0x4F,
+  VKEY_P = 0x50,
+  VKEY_Q = 0x51,
+  VKEY_R = 0x52,
+  VKEY_S = 0x53,
+  VKEY_T = 0x54,
+  VKEY_U = 0x55,
+  VKEY_V = 0x56,
+  VKEY_W = 0x57,
+  VKEY_X = 0x58,
+  VKEY_Y = 0x59,
+  VKEY_Z = 0x5A,
+  VKEY_LWIN = 0x5B,
+  VKEY_COMMAND = VKEY_LWIN,  // Provide the Mac name for convenience.
+  VKEY_RWIN = 0x5C,
+  VKEY_APPS = 0x5D,
+  VKEY_SLEEP = 0x5F,
+  VKEY_NUMPAD0 = 0x60,
+  VKEY_NUMPAD1 = 0x61,
+  VKEY_NUMPAD2 = 0x62,
+  VKEY_NUMPAD3 = 0x63,
+  VKEY_NUMPAD4 = 0x64,
+  VKEY_NUMPAD5 = 0x65,
+  VKEY_NUMPAD6 = 0x66,
+  VKEY_NUMPAD7 = 0x67,
+  VKEY_NUMPAD8 = 0x68,
+  VKEY_NUMPAD9 = 0x69,
+  VKEY_MULTIPLY = 0x6A,
+  VKEY_ADD = 0x6B,
+  VKEY_SEPARATOR = 0x6C,
+  VKEY_SUBTRACT = 0x6D,
+  VKEY_DECIMAL = 0x6E,
+  VKEY_DIVIDE = 0x6F,
+  VKEY_F1 = 0x70,
+  VKEY_F2 = 0x71,
+  VKEY_F3 = 0x72,
+  VKEY_F4 = 0x73,
+  VKEY_F5 = 0x74,
+  VKEY_F6 = 0x75,
+  VKEY_F7 = 0x76,
+  VKEY_F8 = 0x77,
+  VKEY_F9 = 0x78,
+  VKEY_F10 = 0x79,
+  VKEY_F11 = 0x7A,
+  VKEY_F12 = 0x7B,
+  VKEY_F13 = 0x7C,
+  VKEY_F14 = 0x7D,
+  VKEY_F15 = 0x7E,
+  VKEY_F16 = 0x7F,
+  VKEY_F17 = 0x80,
+  VKEY_F18 = 0x81,
+  VKEY_F19 = 0x82,
+  VKEY_F20 = 0x83,
+  VKEY_F21 = 0x84,
+  VKEY_F22 = 0x85,
+  VKEY_F23 = 0x86,
+  VKEY_F24 = 0x87,
+  VKEY_NUMLOCK = 0x90,
+  VKEY_SCROLL = 0x91,
+  VKEY_LSHIFT = 0xA0,
+  VKEY_RSHIFT = 0xA1,
+  VKEY_LCONTROL = 0xA2,
+  VKEY_RCONTROL = 0xA3,
+  VKEY_LMENU = 0xA4,
+  VKEY_RMENU = 0xA5,
+  VKEY_BROWSER_BACK = 0xA6,
+  VKEY_BROWSER_FORWARD = 0xA7,
+  VKEY_BROWSER_REFRESH = 0xA8,
+  VKEY_BROWSER_STOP = 0xA9,
+  VKEY_BROWSER_SEARCH = 0xAA,
+  VKEY_BROWSER_FAVORITES = 0xAB,
+  VKEY_BROWSER_HOME = 0xAC,
+  VKEY_VOLUME_MUTE = 0xAD,
+  VKEY_VOLUME_DOWN = 0xAE,
+  VKEY_VOLUME_UP = 0xAF,
+  VKEY_MEDIA_NEXT_TRACK = 0xB0,
+  VKEY_MEDIA_PREV_TRACK = 0xB1,
+  VKEY_MEDIA_STOP = 0xB2,
+  VKEY_MEDIA_PLAY_PAUSE = 0xB3,
+  VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
+  VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
+  VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
+  VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
+  VKEY_OEM_1 = 0xBA,
+  VKEY_OEM_PLUS = 0xBB,
+  VKEY_OEM_COMMA = 0xBC,
+  VKEY_OEM_MINUS = 0xBD,
+  VKEY_OEM_PERIOD = 0xBE,
+  VKEY_OEM_2 = 0xBF,
+  VKEY_OEM_3 = 0xC0,
+  VKEY_OEM_4 = 0xDB,
+  VKEY_OEM_5 = 0xDC,
+  VKEY_OEM_6 = 0xDD,
+  VKEY_OEM_7 = 0xDE,
+  VKEY_OEM_8 = 0xDF,
+  VKEY_OEM_102 = 0xE2,
+  VKEY_OEM_103 = 0xE3,  // GTV KEYCODE_MEDIA_REWIND
+  VKEY_OEM_104 = 0xE4,  // GTV KEYCODE_MEDIA_FAST_FORWARD
+  VKEY_PROCESSKEY = 0xE5,
+  VKEY_PACKET = 0xE7,
+  VKEY_DBE_SBCSCHAR = 0xF3,
+  VKEY_DBE_DBCSCHAR = 0xF4,
+  VKEY_ATTN = 0xF6,
+  VKEY_CRSEL = 0xF7,
+  VKEY_EXSEL = 0xF8,
+  VKEY_EREOF = 0xF9,
+  VKEY_PLAY = 0xFA,
+  VKEY_ZOOM = 0xFB,
+  VKEY_NONAME = 0xFC,
+  VKEY_PA1 = 0xFD,
+  VKEY_OEM_CLEAR = 0xFE,
+  VKEY_UNKNOWN = 0,
+
+  // POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA,
+  // and 0xE8 are unassigned.
+  VKEY_WLAN = 0x97,
+  VKEY_POWER = 0x98,
+  VKEY_BRIGHTNESS_DOWN = 0xD8,
+  VKEY_BRIGHTNESS_UP = 0xD9,
+  VKEY_KBD_BRIGHTNESS_DOWN = 0xDA,
+  VKEY_KBD_BRIGHTNESS_UP = 0xE8,
+
+  // Windows does not have a specific key code for AltGr. We use the unused 0xE1
+  // (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on
+  // Linux.
+  VKEY_ALTGR = 0xE1,
+#if defined(USE_X11)
+  // Windows does not have a specific key code for Compose. We use the unused
+  // 0xE6 (VK_ICO_CLEAR) code to represent Compose.
+  VKEY_COMPOSE = 0xE6,
+#endif
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODES_POSIX_H_
diff --git a/ui/events/keycodes/keyboard_codes_win.h b/ui/events/keycodes/keyboard_codes_win.h
new file mode 100644
index 0000000..396d38b
--- /dev/null
+++ b/ui/events/keycodes/keyboard_codes_win.h
@@ -0,0 +1,192 @@
+// Copyright (c) 2012 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 UI_EVENTS_KEYCODES_KEYBOARD_CODES_WIN_H_
+#define UI_EVENTS_KEYCODES_KEYBOARD_CODES_WIN_H_
+
+#include <windows.h>
+#include <ime.h>
+
+namespace ui {
+
+enum KeyboardCode {
+  VKEY_BACK = VK_BACK,
+  VKEY_TAB = VK_TAB,
+  VKEY_CLEAR = VK_CLEAR,
+  VKEY_RETURN = VK_RETURN,
+  VKEY_SHIFT = VK_SHIFT,
+  VKEY_CONTROL = VK_CONTROL,
+  VKEY_MENU = VK_MENU,  // a.k.a. ALT
+  VKEY_PAUSE = VK_PAUSE,
+  VKEY_CAPITAL = VK_CAPITAL,
+  VKEY_KANA = VK_KANA,
+  VKEY_HANGUL = VK_HANGUL,
+  VKEY_JUNJA = VK_JUNJA,
+  VKEY_FINAL = VK_FINAL,
+  VKEY_HANJA = VK_HANJA,
+  VKEY_KANJI = VK_KANJI,
+  VKEY_ESCAPE = VK_ESCAPE,
+  VKEY_CONVERT = VK_CONVERT,
+  VKEY_NONCONVERT = VK_NONCONVERT,
+  VKEY_ACCEPT = VK_ACCEPT,
+  VKEY_MODECHANGE = VK_MODECHANGE,
+  VKEY_SPACE = VK_SPACE,
+  VKEY_PRIOR = VK_PRIOR,
+  VKEY_NEXT = VK_NEXT,
+  VKEY_END = VK_END,
+  VKEY_HOME = VK_HOME,
+  VKEY_LEFT = VK_LEFT,
+  VKEY_UP = VK_UP,
+  VKEY_RIGHT = VK_RIGHT,
+  VKEY_DOWN = VK_DOWN,
+  VKEY_SELECT = VK_SELECT,
+  VKEY_PRINT = VK_PRINT,
+  VKEY_EXECUTE = VK_EXECUTE,
+  VKEY_SNAPSHOT = VK_SNAPSHOT,
+  VKEY_INSERT = VK_INSERT,
+  VKEY_DELETE = VK_DELETE,
+  VKEY_HELP = VK_HELP,
+  VKEY_0 = '0',
+  VKEY_1 = '1',
+  VKEY_2 = '2',
+  VKEY_3 = '3',
+  VKEY_4 = '4',
+  VKEY_5 = '5',
+  VKEY_6 = '6',
+  VKEY_7 = '7',
+  VKEY_8 = '8',
+  VKEY_9 = '9',
+  VKEY_A = 'A',
+  VKEY_B = 'B',
+  VKEY_C = 'C',
+  VKEY_D = 'D',
+  VKEY_E = 'E',
+  VKEY_F = 'F',
+  VKEY_G = 'G',
+  VKEY_H = 'H',
+  VKEY_I = 'I',
+  VKEY_J = 'J',
+  VKEY_K = 'K',
+  VKEY_L = 'L',
+  VKEY_M = 'M',
+  VKEY_N = 'N',
+  VKEY_O = 'O',
+  VKEY_P = 'P',
+  VKEY_Q = 'Q',
+  VKEY_R = 'R',
+  VKEY_S = 'S',
+  VKEY_T = 'T',
+  VKEY_U = 'U',
+  VKEY_V = 'V',
+  VKEY_W = 'W',
+  VKEY_X = 'X',
+  VKEY_Y = 'Y',
+  VKEY_Z = 'Z',
+  VKEY_LWIN = VK_LWIN,
+  VKEY_COMMAND = VKEY_LWIN,  // Provide the Mac name for convenience.
+  VKEY_RWIN = VK_RWIN,
+  VKEY_APPS = VK_APPS,
+  VKEY_SLEEP = VK_SLEEP,
+  VKEY_NUMPAD0 = VK_NUMPAD0,
+  VKEY_NUMPAD1 = VK_NUMPAD1,
+  VKEY_NUMPAD2 = VK_NUMPAD2,
+  VKEY_NUMPAD3 = VK_NUMPAD3,
+  VKEY_NUMPAD4 = VK_NUMPAD4,
+  VKEY_NUMPAD5 = VK_NUMPAD5,
+  VKEY_NUMPAD6 = VK_NUMPAD6,
+  VKEY_NUMPAD7 = VK_NUMPAD7,
+  VKEY_NUMPAD8 = VK_NUMPAD8,
+  VKEY_NUMPAD9 = VK_NUMPAD9,
+  VKEY_MULTIPLY = VK_MULTIPLY,
+  VKEY_ADD = VK_ADD,
+  VKEY_SEPARATOR = VK_SEPARATOR,
+  VKEY_SUBTRACT = VK_SUBTRACT,
+  VKEY_DECIMAL = VK_DECIMAL,
+  VKEY_DIVIDE = VK_DIVIDE,
+  VKEY_F1 = VK_F1,
+  VKEY_F2 = VK_F2,
+  VKEY_F3 = VK_F3,
+  VKEY_F4 = VK_F4,
+  VKEY_F5 = VK_F5,
+  VKEY_F6 = VK_F6,
+  VKEY_F7 = VK_F7,
+  VKEY_F8 = VK_F8,
+  VKEY_F9 = VK_F9,
+  VKEY_F10 = VK_F10,
+  VKEY_F11 = VK_F11,
+  VKEY_F12 = VK_F12,
+  VKEY_F13 = VK_F13,
+  VKEY_F14 = VK_F14,
+  VKEY_F15 = VK_F15,
+  VKEY_F16 = VK_F16,
+  VKEY_F17 = VK_F17,
+  VKEY_F18 = VK_F18,
+  VKEY_F19 = VK_F19,
+  VKEY_F20 = VK_F20,
+  VKEY_F21 = VK_F21,
+  VKEY_F22 = VK_F22,
+  VKEY_F23 = VK_F23,
+  VKEY_F24 = VK_F24,
+  VKEY_NUMLOCK = VK_NUMLOCK,
+  VKEY_SCROLL = VK_SCROLL,
+  VKEY_LSHIFT = VK_LSHIFT,
+  VKEY_RSHIFT = VK_RSHIFT,
+  VKEY_LCONTROL = VK_LCONTROL,
+  VKEY_RCONTROL = VK_RCONTROL,
+  VKEY_LMENU = VK_LMENU,
+  VKEY_RMENU = VK_RMENU,
+  VKEY_BROWSER_BACK = VK_BROWSER_BACK,
+  VKEY_BROWSER_FORWARD = VK_BROWSER_FORWARD,
+  VKEY_BROWSER_REFRESH = VK_BROWSER_REFRESH,
+  VKEY_BROWSER_STOP = VK_BROWSER_STOP,
+  VKEY_BROWSER_SEARCH = VK_BROWSER_SEARCH,
+  VKEY_BROWSER_FAVORITES = VK_BROWSER_FAVORITES,
+  VKEY_BROWSER_HOME = VK_BROWSER_HOME,
+  VKEY_VOLUME_MUTE = VK_VOLUME_MUTE,
+  VKEY_VOLUME_DOWN = VK_VOLUME_DOWN,
+  VKEY_VOLUME_UP = VK_VOLUME_UP,
+  VKEY_MEDIA_NEXT_TRACK = VK_MEDIA_NEXT_TRACK,
+  VKEY_MEDIA_PREV_TRACK = VK_MEDIA_PREV_TRACK,
+  VKEY_MEDIA_STOP = VK_MEDIA_STOP,
+  VKEY_MEDIA_PLAY_PAUSE = VK_MEDIA_PLAY_PAUSE,
+  VKEY_MEDIA_LAUNCH_MAIL = 0xB4,
+  VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5,
+  VKEY_MEDIA_LAUNCH_APP1 = 0xB6,
+  VKEY_MEDIA_LAUNCH_APP2 = 0xB7,
+  VKEY_OEM_1 = VK_OEM_1,
+  VKEY_OEM_PLUS = VK_OEM_PLUS,
+  VKEY_OEM_COMMA = VK_OEM_COMMA,
+  VKEY_OEM_MINUS = VK_OEM_MINUS,
+  VKEY_OEM_PERIOD = VK_OEM_PERIOD,
+  VKEY_OEM_2 = VK_OEM_2,
+  VKEY_OEM_3 = VK_OEM_3,
+  VKEY_OEM_4 = VK_OEM_4,
+  VKEY_OEM_5 = VK_OEM_5,
+  VKEY_OEM_6 = VK_OEM_6,
+  VKEY_OEM_7 = VK_OEM_7,
+  VKEY_OEM_8 = VK_OEM_8,
+  VKEY_OEM_102 = VK_OEM_102,
+  VKEY_PROCESSKEY = VK_PROCESSKEY,
+  VKEY_PACKET = VK_PACKET,
+  VKEY_DBE_SBCSCHAR = VK_DBE_SBCSCHAR,
+  VKEY_DBE_DBCSCHAR = VK_DBE_DBCSCHAR,
+  VKEY_ATTN = VK_ATTN,
+  VKEY_CRSEL = VK_CRSEL,
+  VKEY_EXSEL = VK_EXSEL,
+  VKEY_EREOF = VK_EREOF,
+  VKEY_PLAY = VK_PLAY,
+  VKEY_ZOOM = VK_ZOOM,
+  VKEY_NONAME = VK_NONAME,
+  VKEY_PA1 = VK_PA1,
+  VKEY_OEM_CLEAR = VK_OEM_CLEAR,
+  VKEY_UNKNOWN = 0,
+
+  // Windows does not have a specific key code for AltGr. We use the unused
+  // VK_OEM_AX to represent AltGr, matching the behaviour of Firefox on Linux.
+  VKEY_ALTGR = VK_OEM_AX,
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_KEYCODES_KEYBOARD_CODES_WIN_H_
diff --git a/ui/events/latency_info.cc b/ui/events/latency_info.cc
new file mode 100644
index 0000000..5cd1521
--- /dev/null
+++ b/ui/events/latency_info.cc
@@ -0,0 +1,327 @@
+// Copyright 2013 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 "base/debug/trace_event.h"
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/strings/stringprintf.h"
+#include "ui/events/latency_info.h"
+
+#include <algorithm>
+
+namespace {
+
+const size_t kMaxLatencyInfoNumber = 100;
+
+const char* GetComponentName(ui::LatencyComponentType type) {
+#define CASE_TYPE(t) case ui::t:  return #t
+  switch (type) {
+    CASE_TYPE(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_UI_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT);
+    CASE_TYPE(WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT);
+    CASE_TYPE(WINDOW_OLD_SNAPSHOT_FRAME_NUMBER_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_COMMIT_NO_UPDATE_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT);
+    CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT);
+    default:
+      DLOG(WARNING) << "Unhandled LatencyComponentType.\n";
+      break;
+  }
+#undef CASE_TYPE
+  return "unknown";
+}
+
+bool IsTerminalComponent(ui::LatencyComponentType type) {
+  switch (type) {
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_COMMIT_NO_UPDATE_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT:
+    case ui::INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT:
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool IsBeginComponent(ui::LatencyComponentType type) {
+  return (type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT ||
+          type == ui::INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT ||
+          type == ui::INPUT_EVENT_LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT);
+}
+
+// This class is for converting latency info to trace buffer friendly format.
+class LatencyInfoTracedValue : public base::debug::ConvertableToTraceFormat {
+ public:
+  static scoped_refptr<ConvertableToTraceFormat> FromValue(
+      scoped_ptr<base::Value> value);
+
+  virtual void AppendAsTraceFormat(std::string* out) const OVERRIDE;
+
+ private:
+  explicit LatencyInfoTracedValue(base::Value* value);
+  virtual ~LatencyInfoTracedValue();
+
+  scoped_ptr<base::Value> value_;
+
+  DISALLOW_COPY_AND_ASSIGN(LatencyInfoTracedValue);
+};
+
+scoped_refptr<base::debug::ConvertableToTraceFormat>
+LatencyInfoTracedValue::FromValue(scoped_ptr<base::Value> value) {
+  return scoped_refptr<base::debug::ConvertableToTraceFormat>(
+      new LatencyInfoTracedValue(value.release()));
+}
+
+LatencyInfoTracedValue::~LatencyInfoTracedValue() {
+}
+
+void LatencyInfoTracedValue::AppendAsTraceFormat(std::string* out) const {
+  std::string tmp;
+  base::JSONWriter::Write(value_.get(), &tmp);
+  *out += tmp;
+}
+
+LatencyInfoTracedValue::LatencyInfoTracedValue(base::Value* value)
+    : value_(value) {
+}
+
+// Converts latencyinfo into format that can be dumped into trace buffer.
+scoped_refptr<base::debug::ConvertableToTraceFormat> AsTraceableData(
+    const ui::LatencyInfo& latency) {
+  scoped_ptr<base::DictionaryValue> record_data(new base::DictionaryValue());
+  for (ui::LatencyInfo::LatencyMap::const_iterator it =
+           latency.latency_components.begin();
+       it != latency.latency_components.end(); ++it) {
+    base::DictionaryValue* component_info = new base::DictionaryValue();
+    component_info->SetDouble("comp_id", it->first.second);
+    component_info->SetDouble("time", it->second.event_time.ToInternalValue());
+    component_info->SetDouble("count", it->second.event_count);
+    record_data->Set(GetComponentName(it->first.first), component_info);
+  }
+  record_data->SetDouble("trace_id", latency.trace_id);
+
+  scoped_ptr<base::ListValue> coordinates(new base::ListValue());
+  for (size_t i = 0; i < latency.input_coordinates_size; i++) {
+    scoped_ptr<base::DictionaryValue> coordinate_pair(
+        new base::DictionaryValue());
+    coordinate_pair->SetDouble("x", latency.input_coordinates[i].x);
+    coordinate_pair->SetDouble("y", latency.input_coordinates[i].y);
+    coordinates->Append(coordinate_pair.release());
+  }
+  record_data->Set("coordinates", coordinates.release());
+  return LatencyInfoTracedValue::FromValue(record_data.PassAs<base::Value>());
+}
+
+}  // namespace
+
+namespace ui {
+
+LatencyInfo::InputCoordinate::InputCoordinate() : x(0), y(0) {
+}
+
+LatencyInfo::InputCoordinate::InputCoordinate(float x, float y) : x(x), y(y) {
+}
+
+LatencyInfo::LatencyInfo()
+    : input_coordinates_size(0), trace_id(-1), terminated(false) {
+}
+
+LatencyInfo::~LatencyInfo() {
+}
+
+bool LatencyInfo::Verify(const std::vector<LatencyInfo>& latency_info,
+                         const char* referring_msg) {
+  if (latency_info.size() > kMaxLatencyInfoNumber) {
+    LOG(ERROR) << referring_msg << ", LatencyInfo vector size "
+               << latency_info.size() << " is too big.";
+    return false;
+  }
+  for (size_t i = 0; i < latency_info.size(); i++) {
+    if (latency_info[i].input_coordinates_size > kMaxInputCoordinates) {
+      LOG(ERROR) << referring_msg << ", coordinate vector size "
+                 << latency_info[i].input_coordinates_size << " is too big.";
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void LatencyInfo::CopyLatencyFrom(const LatencyInfo& other,
+                                  LatencyComponentType type) {
+  for (LatencyMap::const_iterator it = other.latency_components.begin();
+       it != other.latency_components.end();
+       ++it) {
+    if (it->first.first == type) {
+      AddLatencyNumberWithTimestamp(it->first.first,
+                                    it->first.second,
+                                    it->second.sequence_number,
+                                    it->second.event_time,
+                                    it->second.event_count);
+    }
+  }
+}
+
+void LatencyInfo::AddNewLatencyFrom(const LatencyInfo& other) {
+    for (LatencyMap::const_iterator it = other.latency_components.begin();
+         it != other.latency_components.end();
+         ++it) {
+      if (!FindLatency(it->first.first, it->first.second, NULL)) {
+        AddLatencyNumberWithTimestamp(it->first.first,
+                                      it->first.second,
+                                      it->second.sequence_number,
+                                      it->second.event_time,
+                                      it->second.event_count);
+      }
+    }
+}
+
+void LatencyInfo::AddLatencyNumber(LatencyComponentType component,
+                                   int64 id,
+                                   int64 component_sequence_number) {
+  AddLatencyNumberWithTimestamp(component, id, component_sequence_number,
+                                base::TimeTicks::HighResNow(), 1);
+}
+
+void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component,
+                                                int64 id,
+                                                int64 component_sequence_number,
+                                                base::TimeTicks time,
+                                                uint32 event_count) {
+
+  static const unsigned char* benchmark_enabled =
+      TRACE_EVENT_API_GET_CATEGORY_GROUP_ENABLED("benchmark");
+
+  if (IsBeginComponent(component)) {
+    // Should only ever add begin component once.
+    CHECK_EQ(-1, trace_id);
+    trace_id = component_sequence_number;
+
+    if (*benchmark_enabled) {
+      // The timestamp for ASYNC_BEGIN trace event is used for drawing the
+      // beginning of the trace event in trace viewer. For better visualization,
+      // for an input event, we want to draw the beginning as when the event is
+      // originally created, e.g. the timestamp of its ORIGINAL/UI_COMPONENT,
+      // not when we actually issue the ASYNC_BEGIN trace event.
+      LatencyComponent component;
+      int64 ts = 0;
+      if (FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+                      0,
+                      &component) ||
+          FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT,
+                      0,
+                      &component)) {
+        // The timestamp stored in ORIGINAL/UI_COMPONENT is using clock
+        // CLOCK_MONOTONIC while TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP0
+        // expects timestamp using CLOCK_MONOTONIC or CLOCK_SYSTEM_TRACE (on
+        // CrOS). So we need to adjust the diff between in CLOCK_MONOTONIC and
+        // CLOCK_SYSTEM_TRACE. Note that the diff is drifting overtime so we
+        // can't use a static value.
+        int64 diff = base::TimeTicks::HighResNow().ToInternalValue() -
+            base::TimeTicks::NowFromSystemTraceTime().ToInternalValue();
+        ts = component.event_time.ToInternalValue() - diff;
+      } else {
+        ts = base::TimeTicks::NowFromSystemTraceTime().ToInternalValue();
+      }
+      TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP0(
+          "benchmark",
+          "InputLatency",
+          TRACE_ID_DONT_MANGLE(trace_id),
+          ts);
+    }
+
+    TRACE_EVENT_FLOW_BEGIN0(
+        "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id));
+  }
+
+  LatencyMap::key_type key = std::make_pair(component, id);
+  LatencyMap::iterator it = latency_components.find(key);
+  if (it == latency_components.end()) {
+    LatencyComponent info = {component_sequence_number, time, event_count};
+    latency_components[key] = info;
+  } else {
+    it->second.sequence_number = std::max(component_sequence_number,
+                                          it->second.sequence_number);
+    uint32 new_count = event_count + it->second.event_count;
+    if (event_count > 0 && new_count != 0) {
+      // Do a weighted average, so that the new event_time is the average of
+      // the times of events currently in this structure with the time passed
+      // into this method.
+      it->second.event_time += (time - it->second.event_time) * event_count /
+          new_count;
+      it->second.event_count = new_count;
+    }
+  }
+
+  if (IsTerminalComponent(component) && trace_id != -1) {
+    // Should only ever add terminal component once.
+    CHECK(!terminated);
+    terminated = true;
+
+    if (*benchmark_enabled) {
+      TRACE_EVENT_ASYNC_END1("benchmark",
+                             "InputLatency",
+                             TRACE_ID_DONT_MANGLE(trace_id),
+                             "data", AsTraceableData(*this));
+    }
+
+    TRACE_EVENT_FLOW_END0(
+        "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id));
+  }
+}
+
+bool LatencyInfo::FindLatency(LatencyComponentType type,
+                              int64 id,
+                              LatencyComponent* output) const {
+  LatencyMap::const_iterator it = latency_components.find(
+      std::make_pair(type, id));
+  if (it == latency_components.end())
+    return false;
+  if (output)
+    *output = it->second;
+  return true;
+}
+
+void LatencyInfo::RemoveLatency(LatencyComponentType type) {
+  LatencyMap::iterator it = latency_components.begin();
+  while (it != latency_components.end()) {
+    if (it->first.first == type) {
+      LatencyMap::iterator tmp = it;
+      ++it;
+      latency_components.erase(tmp);
+    } else {
+      it++;
+    }
+  }
+}
+
+void LatencyInfo::Clear() {
+  latency_components.clear();
+}
+
+void LatencyInfo::TraceEventType(const char* event_type) {
+  TRACE_EVENT_ASYNC_STEP_INTO0("benchmark",
+                               "InputLatency",
+                               TRACE_ID_DONT_MANGLE(trace_id),
+                               event_type);
+}
+
+}  // namespace ui
diff --git a/ui/events/latency_info.h b/ui/events/latency_info.h
new file mode 100644
index 0000000..1a18f9b
--- /dev/null
+++ b/ui/events/latency_info.h
@@ -0,0 +1,186 @@
+// Copyright 2013 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 UI_EVENTS_LATENCY_INFO_H_
+#define UI_EVENTS_LATENCY_INFO_H_
+
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/containers/small_map.h"
+#include "base/time/time.h"
+#include "ui/events/events_base_export.h"
+
+namespace ui {
+
+enum LatencyComponentType {
+  // ---------------------------BEGIN COMPONENT-------------------------------
+  // BEGIN COMPONENT is when we show the latency begin in chrome://tracing.
+  // Timestamp when the input event is sent from RenderWidgetHost to renderer.
+  INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+  // Timestamp when the input event is received in plugin.
+  INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT,
+  // Timestamp when a scroll update for the main thread is begun.
+  INPUT_EVENT_LATENCY_BEGIN_SCROLL_UPDATE_MAIN_COMPONENT,
+  // ---------------------------NORMAL COMPONENT-------------------------------
+  // Timestamp when the scroll update gesture event is sent from RWH to
+  // renderer. In Aura, touch event's LatencyInfo is carried over to the gesture
+  // event. So gesture event's INPUT_EVENT_LATENCY_RWH_COMPONENT is the
+  // timestamp when its original touch events is sent from RWH to renderer.
+  // In non-aura platform, INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT
+  // is the same as INPUT_EVENT_LATENCY_RWH_COMPONENT.
+  INPUT_EVENT_LATENCY_SCROLL_UPDATE_RWH_COMPONENT,
+  // The original timestamp of the touch event which converts to scroll update.
+  INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
+  // Original timestamp for input event (e.g. timestamp from kernel).
+  INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+  // Timestamp when the UI event is created.
+  INPUT_EVENT_LATENCY_UI_COMPONENT,
+  // This is special component indicating there is rendering scheduled for
+  // the event associated with this LatencyInfo.
+  INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT,
+  // Timestamp when a scroll update is forwarded to the main thread.
+  INPUT_EVENT_LATENCY_FORWARD_SCROLL_UPDATE_TO_MAIN_COMPONENT,
+  // Timestamp when the touch event is acked.
+  INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT,
+  // Frame number when a window snapshot was requested. The snapshot
+  // is taken when the rendering results actually reach the screen.
+  WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+  // Frame number for a snapshot requested via
+  // gpuBenchmarking.beginWindowSnapshotPNG
+  // TODO(vkuzkokov): remove when patch adding this hits Stable
+  WINDOW_OLD_SNAPSHOT_FRAME_NUMBER_COMPONENT,
+  // Timestamp when a tab is requested to be shown.
+  TAB_SHOW_COMPONENT,
+  // Timestamp of when the Browser process began compositing
+  INPUT_EVENT_BROWSER_COMPOSITE_COMPONENT,
+  // Timestamp of when the Browser process began swap buffers
+  INPUT_EVENT_BROWSER_SWAP_BUFFER_COMPONENT,
+  // Timestamp of when the gpu service began swap buffers, unlike
+  // INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT which measure after
+  INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT,
+  // ---------------------------TERMINAL COMPONENT-----------------------------
+  // TERMINAL COMPONENT is when we show the latency end in chrome://tracing.
+  // Timestamp when the mouse event is acked from renderer and it does not
+  // cause any rendering scheduled.
+  INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT,
+  // Timestamp when the touch event is acked from renderer and it does not
+  // cause any rendering schedueld and does not generate any gesture event.
+  INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT,
+  // Timestamp when the gesture event is acked from renderer, and it does not
+  // cause any rendering schedueld.
+  INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT,
+  // Timestamp when the frame is swapped (i.e. when the rendering caused by
+  // input event actually takes effect).
+  INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT,
+  // This component indicates that the input causes a commit to be scheduled
+  // but the commit failed.
+  INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT,
+  // This component indicates that the input causes a commit to be scheduled
+  // but the commit was aborted since it carried no new information.
+  INPUT_EVENT_LATENCY_TERMINATED_COMMIT_NO_UPDATE_COMPONENT,
+  // This component indicates that the input causes a swap to be scheduled
+  // but the swap failed.
+  INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT,
+  // Timestamp when the input event is considered not cause any rendering
+  // damage in plugin and thus terminated.
+  INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT,
+  LATENCY_COMPONENT_TYPE_LAST = INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT
+};
+
+struct EVENTS_BASE_EXPORT LatencyInfo {
+  struct LatencyComponent {
+    // Nondecreasing number that can be used to determine what events happened
+    // in the component at the time this struct was sent on to the next
+    // component.
+    int64 sequence_number;
+    // Average time of events that happened in this component.
+    base::TimeTicks event_time;
+    // Count of events that happened in this component
+    uint32 event_count;
+  };
+
+  struct EVENTS_BASE_EXPORT InputCoordinate {
+    InputCoordinate();
+    InputCoordinate(float x, float y);
+
+    float x;
+    float y;
+  };
+
+  // Empirically determined constant based on a typical scroll sequence.
+  enum { kTypicalMaxComponentsPerLatencyInfo = 9 };
+
+  enum { kMaxInputCoordinates = 2 };
+
+  // Map a Latency Component (with a component-specific int64 id) to a
+  // component info.
+  typedef base::SmallMap<
+      std::map<std::pair<LatencyComponentType, int64>, LatencyComponent>,
+      kTypicalMaxComponentsPerLatencyInfo> LatencyMap;
+
+  LatencyInfo();
+
+  ~LatencyInfo();
+
+  // Returns true if the vector |latency_info| is valid. Returns false
+  // if it is not valid and log the |referring_msg|.
+  // This function is mainly used to check the latency_info vector that
+  // is passed between processes using IPC message has reasonable size
+  // so that we are confident the IPC message is not corrupted/compromised.
+  // This check will go away once the IPC system has better built-in scheme
+  // for corruption/compromise detection.
+  static bool Verify(const std::vector<LatencyInfo>& latency_info,
+                     const char* referring_msg);
+
+  // Copy LatencyComponents with type |type| from |other| into |this|.
+  void CopyLatencyFrom(const LatencyInfo& other, LatencyComponentType type);
+
+  // Add LatencyComponents that are in |other| but not in |this|.
+  void AddNewLatencyFrom(const LatencyInfo& other);
+
+  // Modifies the current sequence number for a component, and adds a new
+  // sequence number with the current timestamp.
+  void AddLatencyNumber(LatencyComponentType component,
+                        int64 id,
+                        int64 component_sequence_number);
+
+  // Modifies the current sequence number and adds a certain number of events
+  // for a specific component.
+  void AddLatencyNumberWithTimestamp(LatencyComponentType component,
+                                     int64 id,
+                                     int64 component_sequence_number,
+                                     base::TimeTicks time,
+                                     uint32 event_count);
+
+  // Returns true if the a component with |type| and |id| is found in
+  // the latency_components and the component is stored to |output| if
+  // |output| is not NULL. Returns false if no such component is found.
+  bool FindLatency(LatencyComponentType type,
+                   int64 id,
+                   LatencyComponent* output) const;
+
+  void RemoveLatency(LatencyComponentType type);
+
+  void Clear();
+
+  // Records the |event_type| in trace buffer as TRACE_EVENT_ASYNC_STEP.
+  void TraceEventType(const char* event_type);
+
+  LatencyMap latency_components;
+
+  // These coordinates represent window coordinates of the original input event.
+  uint32 input_coordinates_size;
+  InputCoordinate input_coordinates[kMaxInputCoordinates];
+
+  // The unique id for matching the ASYNC_BEGIN/END trace event.
+  int64 trace_id;
+  // Whether a terminal component has been added.
+  bool terminated;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_LATENCY_INFO_H_
diff --git a/ui/events/latency_info_nacl.gyp b/ui/events/latency_info_nacl.gyp
new file mode 100644
index 0000000..e42dbf8
--- /dev/null
+++ b/ui/events/latency_info_nacl.gyp
@@ -0,0 +1,78 @@
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'includes': [
+   '../../build/common_untrusted.gypi',
+  ],
+  'conditions': [
+    ['disable_nacl==0 and disable_nacl_untrusted==0', {
+      'targets': [
+        {
+          'target_name': 'latency_info_nacl',
+          'type': 'none',
+          'defines': [
+            'EVENTS_BASE_IMPLEMENTATION',
+            'EVENTS_IMPLEMENTATION',
+          ],
+          'include_dirs': [
+            '../..',
+          ],
+          'dependencies': [
+            '<(DEPTH)/base/base_nacl.gyp:base_nacl',
+            '<(DEPTH)/ipc/ipc_nacl.gyp:ipc_nacl',
+            '<(DEPTH)/native_client/tools.gyp:prep_toolchain'
+          ],
+          'variables': {
+            'nacl_untrusted_build': 1,
+            'nlib_target': 'liblatency_info_nacl.a',
+            'build_glibc': 0,
+            'build_newlib': 0,
+            'build_irt': 1,
+          },
+          'sources': [
+            'latency_info.cc',
+            'latency_info.h',
+            'ipc/latency_info_param_traits.cc',
+            'ipc/latency_info_param_traits.h',
+          ],
+        },
+      ],
+    }],
+    ['disable_nacl!=1 and OS=="win" and target_arch=="ia32"', {
+      'targets': [
+        {
+          'target_name': 'latency_info_nacl_win64',
+          'type' : '<(component)',
+          'variables': {
+            'nacl_win64_target': 1,
+          },
+          'dependencies': [
+            '<(DEPTH)/base/base.gyp:base_win64',
+            '<(DEPTH)/ipc/ipc.gyp:ipc_win64',
+          ],
+          'defines': [
+            'EVENTS_BASE_IMPLEMENTATION',
+            'EVENTS_IMPLEMENTATION',
+            '<@(nacl_win64_defines)',
+          ],
+          'include_dirs': [
+            '../..',
+          ],
+          'sources': [
+            'latency_info.cc',
+            'latency_info.h',
+            'ipc/latency_info_param_traits.cc',
+            'ipc/latency_info_param_traits.h',
+          ],
+          'configurations': {
+            'Common_Base': {
+              'msvs_target_platform': 'x64',
+            },
+          },
+        },
+      ],
+    }],
+  ],
+}
+
diff --git a/ui/events/latency_info_unittest.cc b/ui/events/latency_info_unittest.cc
new file mode 100644
index 0000000..2f9ad05
--- /dev/null
+++ b/ui/events/latency_info_unittest.cc
@@ -0,0 +1,81 @@
+// Copyright 2013 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 "ui/events/latency_info.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ui {
+
+TEST(LatencyInfoTest, AddTwoSeparateEvent) {
+  LatencyInfo info;
+  info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
+                                     0,
+                                     1,
+                                     base::TimeTicks::FromInternalValue(100),
+                                     1);
+  info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+                                     1,
+                                     5,
+                                     base::TimeTicks::FromInternalValue(1000),
+                                     2);
+
+  EXPECT_EQ(info.latency_components.size(), 2u);
+  LatencyInfo::LatencyComponent component;
+  EXPECT_FALSE(
+      info.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component));
+  EXPECT_FALSE(
+      info.FindLatency(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 1, &component));
+  EXPECT_TRUE(
+      info.FindLatency(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0, &component));
+  EXPECT_EQ(component.sequence_number, 1);
+  EXPECT_EQ(component.event_count, 1u);
+  EXPECT_EQ(component.event_time.ToInternalValue(), 100);
+  EXPECT_TRUE(
+      info.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, &component));
+  EXPECT_EQ(component.sequence_number, 5);
+  EXPECT_EQ(component.event_count, 2u);
+  EXPECT_EQ(component.event_time.ToInternalValue(), 1000);
+}
+
+TEST(LatencyInfoTest, AddTwoSameEvent) {
+  LatencyInfo info;
+  info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+                                     0,
+                                     30,
+                                     base::TimeTicks::FromInternalValue(100),
+                                     2);
+  info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+                                     0,
+                                     13,
+                                     base::TimeTicks::FromInternalValue(200),
+                                     3);
+
+  EXPECT_EQ(info.latency_components.size(), 1u);
+  LatencyInfo::LatencyComponent component;
+  EXPECT_FALSE(
+      info.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component));
+  EXPECT_FALSE(
+      info.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, &component));
+  EXPECT_TRUE(
+      info.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, &component));
+  EXPECT_EQ(component.sequence_number, 30);
+  EXPECT_EQ(component.event_count, 5u);
+  EXPECT_EQ(component.event_time.ToInternalValue(), (100 * 2 + 200 * 3) / 5);
+}
+
+TEST(LatencyInfoTest, ClearEvents) {
+  LatencyInfo info;
+  info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
+                                     0,
+                                     30,
+                                     base::TimeTicks::FromInternalValue(100),
+                                     2);
+
+  EXPECT_EQ(info.latency_components.size(), 1u);
+  info.Clear();
+  EXPECT_EQ(info.latency_components.size(), 0u);
+}
+
+}  // namespace ui
diff --git a/ui/events/linux/text_edit_command_auralinux.cc b/ui/events/linux/text_edit_command_auralinux.cc
new file mode 100644
index 0000000..4429225
--- /dev/null
+++ b/ui/events/linux/text_edit_command_auralinux.cc
@@ -0,0 +1,124 @@
+// Copyright 2014 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 "ui/events/linux/text_edit_command_auralinux.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+std::string TextEditCommandAuraLinux::GetCommandString() const {
+  std::string base_name;
+  switch (command_id_) {
+    case COPY:
+      base_name = "Copy";
+      break;
+    case CUT:
+      base_name = "Cut";
+      break;
+    case DELETE_BACKWARD:
+      base_name = "DeleteBackward";
+      break;
+    case DELETE_FORWARD:
+      base_name = "DeleteForward";
+      break;
+    case DELETE_TO_BEGINING_OF_LINE:
+      base_name = "DeleteToBeginningOfLine";
+      break;
+    case DELETE_TO_BEGINING_OF_PARAGRAPH:
+      base_name = "DeleteToBeginningOfParagraph";
+      break;
+    case DELETE_TO_END_OF_LINE:
+      base_name = "DeleteToEndOfLine";
+      break;
+    case DELETE_TO_END_OF_PARAGRAPH:
+      base_name = "DeleteToEndOfParagraph";
+      break;
+    case DELETE_WORD_BACKWARD:
+      base_name = "DeleteWordBackward";
+      break;
+    case DELETE_WORD_FORWARD:
+      base_name = "DeleteWordForward";
+      break;
+    case INSERT_TEXT:
+      base_name = "InsertText";
+      break;
+    case MOVE_BACKWARD:
+      base_name = "MoveBackward";
+      break;
+    case MOVE_DOWN:
+      base_name = "MoveDown";
+      break;
+    case MOVE_FORWARD:
+      base_name = "MoveForward";
+      break;
+    case MOVE_LEFT:
+      base_name = "MoveLeft";
+      break;
+    case MOVE_PAGE_DOWN:
+      base_name = "MovePageDown";
+      break;
+    case MOVE_PAGE_UP:
+      base_name = "MovePageUp";
+      break;
+    case MOVE_RIGHT:
+      base_name = "MoveRight";
+      break;
+    case MOVE_TO_BEGINING_OF_DOCUMENT:
+      base_name = "MoveToBeginningOfDocument";
+      break;
+    case MOVE_TO_BEGINING_OF_LINE:
+      base_name = "MoveToBeginningOfLine";
+      break;
+    case MOVE_TO_BEGINING_OF_PARAGRAPH:
+      base_name = "MoveToBeginningOfParagraph";
+      break;
+    case MOVE_TO_END_OF_DOCUMENT:
+      base_name = "MoveToEndOfDocument";
+      break;
+    case MOVE_TO_END_OF_LINE:
+      base_name = "MoveToEndOfLine";
+      break;
+    case MOVE_TO_END_OF_PARAGRAPH:
+      base_name = "MoveToEndOfParagraph";
+      break;
+    case MOVE_UP:
+      base_name = "MoveUp";
+      break;
+    case MOVE_WORD_BACKWARD:
+      base_name = "MoveWordBackward";
+      break;
+    case MOVE_WORD_FORWARD:
+      base_name = "MoveWordForward";
+      break;
+    case MOVE_WORD_LEFT:
+      base_name = "MoveWordLeft";
+      break;
+    case MOVE_WORD_RIGHT:
+      base_name = "MoveWordRight";
+      break;
+    case PASTE:
+      base_name = "Paste";
+      break;
+    case SELECT_ALL:
+      base_name = "SelectAll";
+      break;
+    case SET_MARK:
+      base_name = "SetMark";
+      break;
+    case UNSELECT:
+      base_name = "Unselect";
+      break;
+    case INVALID_COMMAND:
+      NOTREACHED();
+      return std::string();
+  }
+
+  if (extend_selection())
+    base_name += "AndModifySelection";
+
+  return base_name;
+}
+
+}  // namespace ui
diff --git a/ui/events/linux/text_edit_command_auralinux.h b/ui/events/linux/text_edit_command_auralinux.h
new file mode 100644
index 0000000..82d3af2
--- /dev/null
+++ b/ui/events/linux/text_edit_command_auralinux.h
@@ -0,0 +1,82 @@
+// Copyright 2014 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 UI_EVENTS_X_TEXT_EDIT_COMMAND_X11_H_
+#define UI_EVENTS_X_TEXT_EDIT_COMMAND_X11_H_
+
+#include <string>
+
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+// Represents a command that performs a specific operation on text.
+// Copy and assignment are explicitly allowed; these objects live in vectors.
+class EVENTS_EXPORT TextEditCommandAuraLinux {
+ public:
+  enum CommandId {
+    COPY,
+    CUT,
+    DELETE_BACKWARD,
+    DELETE_FORWARD,
+    DELETE_TO_BEGINING_OF_LINE,
+    DELETE_TO_BEGINING_OF_PARAGRAPH,
+    DELETE_TO_END_OF_LINE,
+    DELETE_TO_END_OF_PARAGRAPH,
+    DELETE_WORD_BACKWARD,
+    DELETE_WORD_FORWARD,
+    INSERT_TEXT,
+    MOVE_BACKWARD,
+    MOVE_DOWN,
+    MOVE_FORWARD,
+    MOVE_LEFT,
+    MOVE_PAGE_DOWN,
+    MOVE_PAGE_UP,
+    MOVE_RIGHT,
+    MOVE_TO_BEGINING_OF_DOCUMENT,
+    MOVE_TO_BEGINING_OF_LINE,
+    MOVE_TO_BEGINING_OF_PARAGRAPH,
+    MOVE_TO_END_OF_DOCUMENT,
+    MOVE_TO_END_OF_LINE,
+    MOVE_TO_END_OF_PARAGRAPH,
+    MOVE_UP,
+    MOVE_WORD_BACKWARD,
+    MOVE_WORD_FORWARD,
+    MOVE_WORD_LEFT,
+    MOVE_WORD_RIGHT,
+    PASTE,
+    SELECT_ALL,
+    SET_MARK,
+    UNSELECT,
+    INVALID_COMMAND
+  };
+
+  TextEditCommandAuraLinux(CommandId command_id,
+                     const std::string& argument,
+                     bool extend_selection)
+      : command_id_(command_id),
+        argument_(argument),
+        extend_selection_(extend_selection) {}
+
+  CommandId command_id() const { return command_id_; }
+  const std::string& argument() const { return argument_; }
+  bool extend_selection() const { return extend_selection_; }
+
+  // We communicate these commands back to blink with a string representation.
+  // This will combine the base command name with "AndModifySelection" if we
+  // have |extend_selection_| set.
+  std::string GetCommandString() const;
+
+ private:
+  CommandId command_id_;
+
+  std::string argument_;
+
+  // In addition to executing the command, modify the selection.
+  bool extend_selection_;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_TEXT_EDIT_COMMAND_X11_H_
diff --git a/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc b/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc
new file mode 100644
index 0000000..e6a941b
--- /dev/null
+++ b/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 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 "ui/events/linux/text_edit_key_bindings_delegate_auralinux.h"
+
+namespace ui {
+
+namespace {
+// Optional delegate. Unowned pointer.
+TextEditKeyBindingsDelegateAuraLinux* text_edit_keybinding_delegate_ = 0;
+}
+
+void SetTextEditKeyBindingsDelegate(
+     TextEditKeyBindingsDelegateAuraLinux* delegate) {
+  text_edit_keybinding_delegate_ = delegate;
+}
+
+TextEditKeyBindingsDelegateAuraLinux* GetTextEditKeyBindingsDelegate() {
+  return text_edit_keybinding_delegate_;
+}
+
+}  // namespace ui
diff --git a/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h b/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h
new file mode 100644
index 0000000..d291626
--- /dev/null
+++ b/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h
@@ -0,0 +1,43 @@
+// Copyright 2014 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 UI_EVENTS_X_TEXT_EDIT_KEY_BINDINGS_DELEGATE_X11_H_
+#define UI_EVENTS_X_TEXT_EDIT_KEY_BINDINGS_DELEGATE_X11_H_
+
+#include <vector>
+
+#include "ui/events/events_export.h"
+
+namespace ui {
+class Event;
+class TextEditCommandAuraLinux;
+
+// An interface which can interpret various text editing commands out of key
+// events.
+//
+// On desktop Linux, we've traditionally supported the user's custom
+// keybindings. We need to support this in both content/ and in views/.
+class EVENTS_EXPORT TextEditKeyBindingsDelegateAuraLinux {
+ public:
+  // Matches a key event against the users' platform specific key bindings,
+  // false will be returned if the key event doesn't correspond to a predefined
+  // key binding.  Edit commands matched with |event| will be stored in
+  // |edit_commands|, if |edit_commands| is non-NULL.
+  virtual bool MatchEvent(const ui::Event& event,
+                          std::vector<TextEditCommandAuraLinux>* commands) = 0;
+
+ protected:
+  virtual ~TextEditKeyBindingsDelegateAuraLinux() {}
+};
+
+// Sets/Gets the global TextEditKeyBindingsDelegateAuraLinux. No ownership
+// changes. Can be NULL.
+EVENTS_EXPORT void SetTextEditKeyBindingsDelegate(
+    TextEditKeyBindingsDelegateAuraLinux* delegate);
+EVENTS_EXPORT TextEditKeyBindingsDelegateAuraLinux*
+    GetTextEditKeyBindingsDelegate();
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_TEXT_EDIT_KEY_BINDINGS_DELEGATE_X11_H_
diff --git a/ui/events/ozone/BUILD.gn b/ui/events/ozone/BUILD.gn
new file mode 100644
index 0000000..51ad96d
--- /dev/null
+++ b/ui/events/ozone/BUILD.gn
@@ -0,0 +1,99 @@
+# Copyright 2014 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.
+
+import("//build/config/features.gni")
+import("//build/config/ui.gni")
+
+component("events_ozone") {
+  sources = [
+    "device/device_event.cc",
+    "device/device_event.h",
+    "device/device_event_observer.h",
+    "device/device_manager.cc",
+    "device/device_manager.h",
+    "device/device_manager_manual.cc",
+    "device/device_manager_manual.h",
+    "device/udev/device_manager_udev.cc",
+    "device/udev/device_manager_udev.h",
+    "events_ozone_export.h",
+  ]
+
+  deps = [
+    "//base",
+  ]
+
+  defines = [
+    "EVENTS_OZONE_IMPLEMENTATION",
+  ]
+
+  if (!use_udev) {
+    sources -= [
+      "device/udev/device_manager_udev.cc",
+      "device/udev/device_manager_udev.h",
+    ]
+  }
+
+  if (use_ozone_evdev && use_udev) {
+    deps += [
+      "//device/udev_linux",
+    ]
+  }
+}
+
+component("events_ozone_evdev") {
+  sources = [
+    "evdev/event_converter_evdev.cc",
+    "evdev/event_converter_evdev.h",
+    "evdev/event_device_info.cc",
+    "evdev/event_device_info.h",
+    "evdev/event_factory_evdev.cc",
+    "evdev/event_factory_evdev.h",
+    "evdev/event_modifiers_evdev.cc",
+    "evdev/event_modifiers_evdev.h",
+    "evdev/events_ozone_evdev_export.h",
+    "evdev/key_event_converter_evdev.cc",
+    "evdev/key_event_converter_evdev.h",
+    "evdev/touch_event_converter_evdev.cc",
+    "evdev/touch_event_converter_evdev.h",
+  ]
+
+  defines = [
+    "EVENTS_OZONE_EVDEV_IMPLEMENTATION",
+  ]
+
+  deps = [
+    ":events_ozone",
+    "//base",
+    "//ui/events/platform",
+    "//ui/gfx",
+  ]
+
+  if (use_ozone_evdev) {
+    defines += [
+      "USE_OZONE_EVDEV=1"
+    ]
+  }
+
+  if (use_ozone_evdev && use_evdev_gestures) {
+    sources += [
+      "evdev/libgestures_glue/event_reader_libevdev_cros.cc",
+      "evdev/libgestures_glue/event_reader_libevdev_cros.h",
+      "evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc",
+      "evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h",
+      "evdev/libgestures_glue/gesture_logging.cc",
+      "evdev/libgestures_glue/gesture_logging.h",
+      "evdev/libgestures_glue/gesture_timer_provider.cc",
+      "evdev/libgestures_glue/gesture_timer_provider.h",
+    ]
+
+    defines += [
+      "USE_EVDEV_GESTURES",
+    ]
+
+    configs += [
+      "//build/config/linux:libevdev-cros",
+      "//build/config/linux:libgestures",
+    ]
+  }
+}
diff --git a/ui/events/ozone/DEPS b/ui/events/ozone/DEPS
new file mode 100644
index 0000000..3f27114
--- /dev/null
+++ b/ui/events/ozone/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+device/udev_linux",
+  "+ui/ozone/public",
+]
diff --git a/ui/events/ozone/OWNERS b/ui/events/ozone/OWNERS
new file mode 100644
index 0000000..479c4d8
--- /dev/null
+++ b/ui/events/ozone/OWNERS
@@ -0,0 +1,2 @@
+rjkroege@chromium.org
+spang@chromium.org
diff --git a/ui/events/ozone/device/device_event.cc b/ui/events/ozone/device/device_event.cc
new file mode 100644
index 0000000..e20079a
--- /dev/null
+++ b/ui/events/ozone/device/device_event.cc
@@ -0,0 +1,16 @@
+// Copyright 2014 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 "ui/events/ozone/device/device_event.h"
+
+namespace ui {
+
+DeviceEvent::DeviceEvent(DeviceType type,
+                         ActionType action,
+                         const base::FilePath& path)
+    : device_type_(type),
+      action_type_(action),
+      path_(path) {}
+
+}  // namespace ui
diff --git a/ui/events/ozone/device/device_event.h b/ui/events/ozone/device/device_event.h
new file mode 100644
index 0000000..a24394c
--- /dev/null
+++ b/ui/events/ozone/device/device_event.h
@@ -0,0 +1,43 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_DEVICE_EVENT_H_
+#define UI_EVENTS_OZONE_DEVICE_EVENT_H_
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "ui/events/ozone/events_ozone_export.h"
+
+namespace ui {
+
+class EVENTS_OZONE_EXPORT DeviceEvent {
+ public:
+  enum DeviceType {
+    INPUT,
+    DISPLAY,
+  };
+
+  enum ActionType {
+    ADD,
+    REMOVE,
+    CHANGE,
+  };
+
+  DeviceEvent(DeviceType type, ActionType action, const base::FilePath& path);
+
+  DeviceType device_type() const { return device_type_; }
+  ActionType action_type() const { return action_type_; }
+  base::FilePath path() const { return path_; }
+
+ private:
+  DeviceType device_type_;
+  ActionType action_type_;
+  base::FilePath path_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceEvent);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_DEVICE_EVENT_H_
diff --git a/ui/events/ozone/device/device_event_observer.h b/ui/events/ozone/device/device_event_observer.h
new file mode 100644
index 0000000..7ad2f49
--- /dev/null
+++ b/ui/events/ozone/device/device_event_observer.h
@@ -0,0 +1,24 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_DEVICE_EVENT_OBSERVER_H_
+#define UI_EVENTS_OZONE_DEVICE_EVENT_OBSERVER_H_
+
+#include "ui/events/ozone/events_ozone_export.h"
+
+namespace ui {
+
+class DeviceEvent;
+
+class EVENTS_OZONE_EXPORT DeviceEventObserver {
+ public:
+  virtual ~DeviceEventObserver() {}
+
+  virtual void OnDeviceEvent(const DeviceEvent& event) = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_DEVICE_EVENT_OBSERVER_H_
+
diff --git a/ui/events/ozone/device/device_manager.cc b/ui/events/ozone/device/device_manager.cc
new file mode 100644
index 0000000..bbd5d80
--- /dev/null
+++ b/ui/events/ozone/device/device_manager.cc
@@ -0,0 +1,23 @@
+// Copyright 2014 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 "ui/events/ozone/device/device_manager.h"
+
+#if defined(USE_UDEV)
+#include "ui/events/ozone/device/udev/device_manager_udev.h"
+#else
+#include "ui/events/ozone/device/device_manager_manual.h"
+#endif
+
+namespace ui {
+
+scoped_ptr<DeviceManager> CreateDeviceManager() {
+#if defined(USE_UDEV)
+  return scoped_ptr<DeviceManager>(new DeviceManagerUdev());
+#else
+  return scoped_ptr<DeviceManager>(new DeviceManagerManual());
+#endif
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/device/device_manager.h b/ui/events/ozone/device/device_manager.h
new file mode 100644
index 0000000..0551e7e
--- /dev/null
+++ b/ui/events/ozone/device/device_manager.h
@@ -0,0 +1,36 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_H_
+#define UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/ozone/events_ozone_export.h"
+
+namespace ui {
+
+class DeviceEventObserver;
+
+class EVENTS_OZONE_EXPORT DeviceManager {
+ public:
+  virtual ~DeviceManager() {}
+
+  // Scans the currently available devices and notifies |observer| for each
+  // device found. If also registering for notifications through AddObserver(),
+  // the scan should happen after the registration otherwise the observer may
+  // miss events.
+  virtual void ScanDevices(DeviceEventObserver* observer) = 0;
+
+  // Registers |observer| for device event notifications.
+  virtual void AddObserver(DeviceEventObserver* observer) = 0;
+
+  // Removes |observer| from the list of observers notified.
+  virtual void RemoveObserver(DeviceEventObserver* observer) = 0;
+};
+
+EVENTS_OZONE_EXPORT scoped_ptr<DeviceManager> CreateDeviceManager();
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_H_
diff --git a/ui/events/ozone/device/device_manager_manual.cc b/ui/events/ozone/device/device_manager_manual.cc
new file mode 100644
index 0000000..c86ffcc
--- /dev/null
+++ b/ui/events/ozone/device/device_manager_manual.cc
@@ -0,0 +1,33 @@
+// Copyright 2014 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 "ui/events/ozone/device/device_manager_manual.h"
+
+#include "base/files/file_enumerator.h"
+#include "ui/events/ozone/device/device_event.h"
+#include "ui/events/ozone/device/device_event_observer.h"
+
+namespace ui {
+
+DeviceManagerManual::DeviceManagerManual() {}
+
+DeviceManagerManual::~DeviceManagerManual() {}
+
+void DeviceManagerManual::ScanDevices(DeviceEventObserver* observer) {
+  base::FileEnumerator file_enum(base::FilePath("/dev/input"),
+                                 false,
+                                 base::FileEnumerator::FILES,
+                                 "event*[0-9]");
+  for (base::FilePath path = file_enum.Next(); !path.empty();
+       path = file_enum.Next()) {
+    DeviceEvent event(DeviceEvent::INPUT, DeviceEvent::ADD, path);
+    observer->OnDeviceEvent(event);
+  }
+}
+
+void DeviceManagerManual::AddObserver(DeviceEventObserver* observer) {}
+
+void DeviceManagerManual::RemoveObserver(DeviceEventObserver* observer) {}
+
+}  // namespace ui
diff --git a/ui/events/ozone/device/device_manager_manual.h b/ui/events/ozone/device/device_manager_manual.h
new file mode 100644
index 0000000..9d1cf61
--- /dev/null
+++ b/ui/events/ozone/device/device_manager_manual.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_MANUAL_H_
+#define UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_MANUAL_H_
+
+#include "base/macros.h"
+#include "ui/events/ozone/device/device_manager.h"
+
+namespace ui {
+
+class DeviceManagerManual : public DeviceManager {
+ public:
+  DeviceManagerManual();
+  virtual ~DeviceManagerManual();
+
+ private:
+  // DeviceManager overrides:
+  virtual void ScanDevices(DeviceEventObserver* observer) OVERRIDE;
+  virtual void AddObserver(DeviceEventObserver* observer) OVERRIDE;
+  virtual void RemoveObserver(DeviceEventObserver* observer) OVERRIDE;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceManagerManual);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_DEVICE_DEVICE_MANAGER_MANUAL_H_
diff --git a/ui/events/ozone/device/udev/device_manager_udev.cc b/ui/events/ozone/device/udev/device_manager_udev.cc
new file mode 100644
index 0000000..7f86bee
--- /dev/null
+++ b/ui/events/ozone/device/udev/device_manager_udev.cc
@@ -0,0 +1,186 @@
+// Copyright 2014 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 "ui/events/ozone/device/udev/device_manager_udev.h"
+
+#include <libudev.h>
+
+#include "base/debug/trace_event.h"
+#include "base/strings/stringprintf.h"
+#include "ui/events/ozone/device/device_event.h"
+#include "ui/events/ozone/device/device_event_observer.h"
+
+namespace ui {
+
+namespace {
+
+const char* kSubsystems[] = {
+  "input",
+  "drm",
+};
+
+// Severity levels from syslog.h. We can't include it directly as it
+// conflicts with base/logging.h
+enum {
+  SYS_LOG_EMERG = 0,
+  SYS_LOG_ALERT = 1,
+  SYS_LOG_CRIT = 2,
+  SYS_LOG_ERR = 3,
+  SYS_LOG_WARNING = 4,
+  SYS_LOG_NOTICE = 5,
+  SYS_LOG_INFO = 6,
+  SYS_LOG_DEBUG = 7,
+};
+
+// Log handler for messages generated from libudev.
+void UdevLog(struct udev* udev,
+             int priority,
+             const char* file,
+             int line,
+             const char* fn,
+             const char* format,
+             va_list args) {
+  if (priority <= SYS_LOG_ERR)
+    LOG(ERROR) << "libudev: " << fn << ": " << base::StringPrintV(format, args);
+  else if (priority <= SYS_LOG_INFO)
+    VLOG(1) << "libudev: " << fn << ": " << base::StringPrintV(format, args);
+  else  // SYS_LOG_DEBUG
+    VLOG(2) << "libudev: " << fn << ": " << base::StringPrintV(format, args);
+}
+
+// Create libudev context.
+device::ScopedUdevPtr UdevCreate() {
+  struct udev* udev = udev_new();
+  if (udev) {
+    udev_set_log_fn(udev, UdevLog);
+    udev_set_log_priority(udev, SYS_LOG_DEBUG);
+  }
+  return device::ScopedUdevPtr(udev);
+}
+
+// Start monitoring input device changes.
+device::ScopedUdevMonitorPtr UdevCreateMonitor(struct udev* udev) {
+  struct udev_monitor* monitor = udev_monitor_new_from_netlink(udev, "udev");
+  if (monitor) {
+    for (size_t i = 0; i < arraysize(kSubsystems); ++i)
+      udev_monitor_filter_add_match_subsystem_devtype(
+          monitor, kSubsystems[i], NULL);
+
+    if (udev_monitor_enable_receiving(monitor))
+      LOG(ERROR) << "Failed to start receiving events from udev";
+  } else {
+    LOG(ERROR) << "Failed to create udev monitor";
+  }
+
+  return device::ScopedUdevMonitorPtr(monitor);
+}
+
+}  // namespace
+
+DeviceManagerUdev::DeviceManagerUdev() : udev_(UdevCreate()) {
+}
+
+DeviceManagerUdev::~DeviceManagerUdev() {
+}
+
+void DeviceManagerUdev::CreateMonitor() {
+  if (monitor_)
+    return;
+  monitor_ = UdevCreateMonitor(udev_.get());
+  if (monitor_) {
+    int fd = udev_monitor_get_fd(monitor_.get());
+    CHECK_GT(fd, 0);
+    base::MessageLoopForUI::current()->WatchFileDescriptor(
+        fd, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this);
+  }
+}
+
+void DeviceManagerUdev::ScanDevices(DeviceEventObserver* observer) {
+  CreateMonitor();
+
+  device::ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get()));
+  if (!enumerate)
+    return;
+
+  for (size_t i = 0; i < arraysize(kSubsystems); ++i)
+    udev_enumerate_add_match_subsystem(enumerate.get(), kSubsystems[i]);
+  udev_enumerate_scan_devices(enumerate.get());
+
+  struct udev_list_entry* devices =
+      udev_enumerate_get_list_entry(enumerate.get());
+  struct udev_list_entry* entry;
+
+  udev_list_entry_foreach(entry, devices) {
+    device::ScopedUdevDevicePtr device(udev_device_new_from_syspath(
+        udev_.get(), udev_list_entry_get_name(entry)));
+    if (!device)
+      continue;
+
+    scoped_ptr<DeviceEvent> event = ProcessMessage(device.get());
+    if (event)
+      observer->OnDeviceEvent(*event.get());
+  }
+}
+
+void DeviceManagerUdev::AddObserver(DeviceEventObserver* observer) {
+  observers_.AddObserver(observer);
+}
+
+void DeviceManagerUdev::RemoveObserver(DeviceEventObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void DeviceManagerUdev::OnFileCanReadWithoutBlocking(int fd) {
+  // The netlink socket should never become disconnected. There's no need
+  // to handle broken connections here.
+  TRACE_EVENT1("ozone", "UdevDeviceChange", "socket", fd);
+
+  device::ScopedUdevDevicePtr device(
+      udev_monitor_receive_device(monitor_.get()));
+  if (!device)
+    return;
+
+  scoped_ptr<DeviceEvent> event = ProcessMessage(device.get());
+  if (event)
+    FOR_EACH_OBSERVER(
+        DeviceEventObserver, observers_, OnDeviceEvent(*event.get()));
+}
+
+void DeviceManagerUdev::OnFileCanWriteWithoutBlocking(int fd) {
+  NOTREACHED();
+}
+
+scoped_ptr<DeviceEvent> DeviceManagerUdev::ProcessMessage(udev_device* device) {
+  const char* path = udev_device_get_devnode(device);
+  const char* action = udev_device_get_action(device);
+  const char* hotplug = udev_device_get_property_value(device, "HOTPLUG");
+  const char* subsystem = udev_device_get_property_value(device, "SUBSYSTEM");
+
+  if (!path || !subsystem)
+    return scoped_ptr<DeviceEvent>();
+
+  DeviceEvent::DeviceType device_type;
+  if (!strcmp(subsystem, "input") &&
+      StartsWithASCII(path, "/dev/input/event", true))
+    device_type = DeviceEvent::INPUT;
+  else if (!strcmp(subsystem, "drm") && hotplug && !strcmp(hotplug, "1"))
+    device_type = DeviceEvent::DISPLAY;
+  else
+    return scoped_ptr<DeviceEvent>();
+
+  DeviceEvent::ActionType action_type;
+  if (!action || !strcmp(action, "add"))
+    action_type = DeviceEvent::ADD;
+  else if (!strcmp(action, "remove"))
+    action_type = DeviceEvent::REMOVE;
+  else if (!strcmp(action, "change"))
+    action_type = DeviceEvent::CHANGE;
+  else
+    return scoped_ptr<DeviceEvent>();
+
+  return scoped_ptr<DeviceEvent>(
+      new DeviceEvent(device_type, action_type, base::FilePath(path)));
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/device/udev/device_manager_udev.h b/ui/events/ozone/device/udev/device_manager_udev.h
new file mode 100644
index 0000000..8a7537a
--- /dev/null
+++ b/ui/events/ozone/device/udev/device_manager_udev.h
@@ -0,0 +1,51 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_DEVICE_UDEV_DEVICE_MANAGER_UDEV_H_
+#define UI_EVENTS_OZONE_DEVICE_UDEV_DEVICE_MANAGER_UDEV_H_
+
+#include "base/message_loop/message_pump_libevent.h"
+#include "base/observer_list.h"
+#include "device/udev_linux/udev.h"
+#include "ui/events/ozone/device/device_manager.h"
+
+namespace ui {
+
+class DeviceEvent;
+class DeviceEventObserver;
+
+class DeviceManagerUdev
+    : public DeviceManager, base::MessagePumpLibevent::Watcher {
+ public:
+  DeviceManagerUdev();
+  virtual ~DeviceManagerUdev();
+
+ private:
+  scoped_ptr<DeviceEvent> ProcessMessage(udev_device* device);
+
+  // Creates a device-monitor to look for device add/remove/change events.
+  void CreateMonitor();
+
+  // DeviceManager overrides:
+  virtual void ScanDevices(DeviceEventObserver* observer) OVERRIDE;
+  virtual void AddObserver(DeviceEventObserver* observer) OVERRIDE;
+  virtual void RemoveObserver(DeviceEventObserver* observer) OVERRIDE;
+
+  // base::MessagePumpLibevent::Watcher overrides:
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+  device::ScopedUdevPtr udev_;
+  device::ScopedUdevMonitorPtr monitor_;
+
+  base::MessagePumpLibevent::FileDescriptorWatcher controller_;
+
+  ObserverList<DeviceEventObserver> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceManagerUdev);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_DEVICE_UDEV_DEVICE_MANAGER_UDEV_H_
diff --git a/ui/events/ozone/evdev/cursor_delegate_evdev.h b/ui/events/ozone/evdev/cursor_delegate_evdev.h
new file mode 100644
index 0000000..63947b3
--- /dev/null
+++ b/ui/events/ozone/evdev/cursor_delegate_evdev.h
@@ -0,0 +1,34 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_
+
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class Vector2dF;
+}
+
+namespace ui {
+
+class EVENTS_OZONE_EVDEV_EXPORT CursorDelegateEvdev {
+ public:
+  // Move the cursor.
+  virtual void MoveCursor(const gfx::Vector2dF& delta) = 0;
+  virtual void MoveCursorTo(gfx::AcceleratedWidget widget,
+                            const gfx::PointF& location) = 0;
+
+  // Location in window.
+  virtual gfx::PointF location() = 0;
+
+  // Cursor visibility.
+  virtual bool IsCursorVisible() = 0;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_
diff --git a/ui/events/ozone/evdev/event_converter_evdev.cc b/ui/events/ozone/evdev/event_converter_evdev.cc
new file mode 100644
index 0000000..518c7f2
--- /dev/null
+++ b/ui/events/ozone/evdev/event_converter_evdev.cc
@@ -0,0 +1,45 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/event_converter_evdev.h"
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+
+namespace ui {
+
+EventConverterEvdev::EventConverterEvdev(int fd,
+                                         const base::FilePath& path,
+                                         int id)
+    : fd_(fd), path_(path), id_(id) {
+}
+
+EventConverterEvdev::~EventConverterEvdev() {
+  Stop();
+}
+
+void EventConverterEvdev::Start() {
+  base::MessageLoopForUI::current()->WatchFileDescriptor(
+      fd_, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this);
+}
+
+void EventConverterEvdev::Stop() {
+  controller_.StopWatchingFileDescriptor();
+}
+
+void EventConverterEvdev::OnFileCanWriteWithoutBlocking(int fd) {
+  NOTREACHED();
+}
+
+bool EventConverterEvdev::HasTouchscreen() const {
+  return false;
+}
+
+gfx::Size EventConverterEvdev::GetTouchscreenSize() const {
+  NOTREACHED();
+  return gfx::Size();
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/event_converter_evdev.h b/ui/events/ozone/evdev/event_converter_evdev.h
new file mode 100644
index 0000000..b4b2463
--- /dev/null
+++ b/ui/events/ozone/evdev/event_converter_evdev.h
@@ -0,0 +1,63 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace ui {
+
+class Event;
+
+typedef base::Callback<void(Event*)> EventDispatchCallback;
+
+class EVENTS_OZONE_EVDEV_EXPORT EventConverterEvdev
+    : public base::MessagePumpLibevent::Watcher {
+ public:
+  EventConverterEvdev(int fd, const base::FilePath& path, int id);
+  virtual ~EventConverterEvdev();
+
+  int id() const { return id_; }
+
+  // Start reading events.
+  void Start();
+
+  // Stop reading events.
+  void Stop();
+
+  // Returns true of the converter is used for a touchscreen device.
+  virtual bool HasTouchscreen() const;
+
+  // Returns the size of the touchscreen device if the converter is used for a
+  // touchscreen device.
+  virtual gfx::Size GetTouchscreenSize() const;
+
+ protected:
+  // base::MessagePumpLibevent::Watcher:
+  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+  // File descriptor to read.
+  int fd_;
+
+  // Path to input device.
+  base::FilePath path_;
+
+  // Uniquely identifies an event converter.
+  int id_;
+
+  // Controller for watching the input fd.
+  base::MessagePumpLibevent::FileDescriptorWatcher controller_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EventConverterEvdev);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_
diff --git a/ui/events/ozone/evdev/event_device_info.cc b/ui/events/ozone/evdev/event_device_info.cc
new file mode 100644
index 0000000..2734b9d
--- /dev/null
+++ b/ui/events/ozone/evdev/event_device_info.cc
@@ -0,0 +1,189 @@
+// Copyright 2013 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 "ui/events/ozone/evdev/event_device_info.h"
+
+#include <linux/input.h>
+
+#include "base/logging.h"
+#include "base/threading/thread_restrictions.h"
+
+namespace ui {
+
+namespace {
+
+bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) {
+  if (ioctl(fd, EVIOCGBIT(type, size), buf) < 0) {
+    DLOG(ERROR) << "failed EVIOCGBIT(" << type << ", " << size << ") on fd "
+                << fd;
+    return false;
+  }
+
+  return true;
+}
+
+bool GetPropBits(int fd, void* buf, unsigned int size) {
+  if (ioctl(fd, EVIOCGPROP(size), buf) < 0) {
+    DLOG(ERROR) << "failed EVIOCGPROP(" << size << ") on fd " << fd;
+    return false;
+  }
+
+  return true;
+}
+
+bool BitIsSet(const unsigned long* bits, unsigned int bit) {
+  return (bits[bit / EVDEV_LONG_BITS] & (1UL << (bit % EVDEV_LONG_BITS)));
+}
+
+bool GetAbsInfo(int fd, int code, struct input_absinfo* absinfo) {
+  if (ioctl(fd, EVIOCGABS(code), absinfo)) {
+    DLOG(ERROR) << "failed EVIOCGABS(" << code << ") on fd " << fd;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+EventDeviceInfo::EventDeviceInfo() {
+  memset(ev_bits_, 0, sizeof(ev_bits_));
+  memset(key_bits_, 0, sizeof(key_bits_));
+  memset(rel_bits_, 0, sizeof(rel_bits_));
+  memset(abs_bits_, 0, sizeof(abs_bits_));
+  memset(msc_bits_, 0, sizeof(msc_bits_));
+  memset(sw_bits_, 0, sizeof(sw_bits_));
+  memset(led_bits_, 0, sizeof(led_bits_));
+  memset(prop_bits_, 0, sizeof(prop_bits_));
+  memset(abs_info_, 0, sizeof(abs_info_));
+}
+
+EventDeviceInfo::~EventDeviceInfo() {}
+
+bool EventDeviceInfo::Initialize(int fd) {
+  if (!GetEventBits(fd, 0, ev_bits_, sizeof(ev_bits_)))
+    return false;
+
+  if (!GetEventBits(fd, EV_KEY, key_bits_, sizeof(key_bits_)))
+    return false;
+
+  if (!GetEventBits(fd, EV_REL, rel_bits_, sizeof(rel_bits_)))
+    return false;
+
+  if (!GetEventBits(fd, EV_ABS, abs_bits_, sizeof(abs_bits_)))
+    return false;
+
+  if (!GetEventBits(fd, EV_MSC, msc_bits_, sizeof(msc_bits_)))
+    return false;
+
+  if (!GetEventBits(fd, EV_SW, sw_bits_, sizeof(sw_bits_)))
+    return false;
+
+  if (!GetEventBits(fd, EV_LED, led_bits_, sizeof(led_bits_)))
+    return false;
+
+  if (!GetPropBits(fd, prop_bits_, sizeof(prop_bits_)))
+    return false;
+
+  for (unsigned int i = 0; i < ABS_CNT; ++i)
+    if (HasAbsEvent(i))
+      if (!GetAbsInfo(fd, i, &abs_info_[i]))
+        return false;
+
+  return true;
+}
+
+bool EventDeviceInfo::HasEventType(unsigned int type) const {
+  if (type > EV_MAX)
+    return false;
+  return BitIsSet(ev_bits_, type);
+}
+
+bool EventDeviceInfo::HasKeyEvent(unsigned int code) const {
+  if (code > KEY_MAX)
+    return false;
+  return BitIsSet(key_bits_, code);
+}
+
+bool EventDeviceInfo::HasRelEvent(unsigned int code) const {
+  if (code > REL_MAX)
+    return false;
+  return BitIsSet(rel_bits_, code);
+}
+
+bool EventDeviceInfo::HasAbsEvent(unsigned int code) const {
+  if (code > ABS_MAX)
+    return false;
+  return BitIsSet(abs_bits_, code);
+}
+
+bool EventDeviceInfo::HasMscEvent(unsigned int code) const {
+  if (code > MSC_MAX)
+    return false;
+  return BitIsSet(msc_bits_, code);
+}
+
+bool EventDeviceInfo::HasSwEvent(unsigned int code) const {
+  if (code > SW_MAX)
+    return false;
+  return BitIsSet(sw_bits_, code);
+}
+
+bool EventDeviceInfo::HasLedEvent(unsigned int code) const {
+  if (code > LED_MAX)
+    return false;
+  return BitIsSet(led_bits_, code);
+}
+
+bool EventDeviceInfo::HasProp(unsigned int code) const {
+  if (code > INPUT_PROP_MAX)
+    return false;
+  return BitIsSet(prop_bits_, code);
+}
+
+int32 EventDeviceInfo::GetAbsMinimum(unsigned int code) const {
+  return abs_info_[code].minimum;
+}
+
+int32 EventDeviceInfo::GetAbsMaximum(unsigned int code) const {
+  return abs_info_[code].maximum;
+}
+
+bool EventDeviceInfo::HasAbsXY() const {
+  if (HasAbsEvent(ABS_X) && HasAbsEvent(ABS_Y))
+    return true;
+
+  if (HasAbsEvent(ABS_MT_POSITION_X) && HasAbsEvent(ABS_MT_POSITION_Y))
+    return true;
+
+  return false;
+}
+
+bool EventDeviceInfo::HasRelXY() const {
+  return HasRelEvent(REL_X) && HasRelEvent(REL_Y);
+}
+
+bool EventDeviceInfo::IsMappedToScreen() const {
+  // Device position is mapped directly to the screen.
+  if (HasProp(INPUT_PROP_DIRECT))
+    return true;
+
+  // Device position moves the cursor.
+  if (HasProp(INPUT_PROP_POINTER))
+    return false;
+
+  // Tablets are mapped to the screen.
+  if (HasKeyEvent(BTN_TOOL_PEN) || HasKeyEvent(BTN_STYLUS) ||
+      HasKeyEvent(BTN_STYLUS2))
+    return true;
+
+  // Touchpads are not mapped to the screen.
+  if (HasKeyEvent(BTN_LEFT) || HasKeyEvent(BTN_MIDDLE) ||
+      HasKeyEvent(BTN_RIGHT) || HasKeyEvent(BTN_TOOL_FINGER))
+    return false;
+
+  // Touchscreens are mapped to the screen.
+  return true;
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/event_device_info.h b/ui/events/ozone/evdev/event_device_info.h
new file mode 100644
index 0000000..492539b
--- /dev/null
+++ b/ui/events/ozone/evdev/event_device_info.h
@@ -0,0 +1,74 @@
+// Copyright 2013 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 UI_EVENTS_OZONE_EVDEV_EVENT_DEVICE_INFO_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_DEVICE_INFO_H_
+
+#include <limits.h>
+#include <linux/input.h>
+
+#include "base/basictypes.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+
+#define EVDEV_LONG_BITS (CHAR_BIT * sizeof(long))
+#define EVDEV_BITS_TO_LONGS(x) (((x) + EVDEV_LONG_BITS - 1) / EVDEV_LONG_BITS)
+
+namespace ui {
+
+// Device information for Linux input devices
+//
+// This stores and queries information about input devices; in
+// particular it knows which events the device can generate.
+class EVENTS_OZONE_EVDEV_EXPORT EventDeviceInfo {
+ public:
+  EventDeviceInfo();
+  ~EventDeviceInfo();
+
+  // Initialize device information from an open device.
+  bool Initialize(int fd);
+
+  // Check events this device can generate.
+  bool HasEventType(unsigned int type) const;
+  bool HasKeyEvent(unsigned int code) const;
+  bool HasRelEvent(unsigned int code) const;
+  bool HasAbsEvent(unsigned int code) const;
+  bool HasMscEvent(unsigned int code) const;
+  bool HasSwEvent(unsigned int code) const;
+  bool HasLedEvent(unsigned int code) const;
+
+  // Properties of absolute axes.
+  int32 GetAbsMinimum(unsigned int code) const;
+  int32 GetAbsMaximum(unsigned int code) const;
+
+  // Check input device properties.
+  bool HasProp(unsigned int code) const;
+
+  // Has absolute X & Y axes.
+  bool HasAbsXY() const;
+
+  // Has relativeX & Y axes.
+  bool HasRelXY() const;
+
+  // Determine whether absolute device X/Y coordinates are mapped onto the
+  // screen. This is the case for touchscreens and tablets but not touchpads.
+  bool IsMappedToScreen() const;
+
+ private:
+  unsigned long ev_bits_[EVDEV_BITS_TO_LONGS(EV_CNT)];
+  unsigned long key_bits_[EVDEV_BITS_TO_LONGS(KEY_CNT)];
+  unsigned long rel_bits_[EVDEV_BITS_TO_LONGS(REL_CNT)];
+  unsigned long abs_bits_[EVDEV_BITS_TO_LONGS(ABS_CNT)];
+  unsigned long msc_bits_[EVDEV_BITS_TO_LONGS(MSC_CNT)];
+  unsigned long sw_bits_[EVDEV_BITS_TO_LONGS(SW_CNT)];
+  unsigned long led_bits_[EVDEV_BITS_TO_LONGS(LED_CNT)];
+  unsigned long prop_bits_[EVDEV_BITS_TO_LONGS(INPUT_PROP_CNT)];
+
+  struct input_absinfo abs_info_[ABS_CNT];
+
+  DISALLOW_COPY_AND_ASSIGN(EventDeviceInfo);
+};
+
+}  // namspace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_EVENT_DEVICE_INFO_H_
diff --git a/ui/events/ozone/evdev/event_factory_evdev.cc b/ui/events/ozone/evdev/event_factory_evdev.cc
new file mode 100644
index 0000000..88ec979
--- /dev/null
+++ b/ui/events/ozone/evdev/event_factory_evdev.cc
@@ -0,0 +1,276 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/event_factory_evdev.h"
+
+#include <fcntl.h>
+#include <linux/input.h>
+
+#include "base/debug/trace_event.h"
+#include "base/stl_util.h"
+#include "base/task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/threading/worker_pool.h"
+#include "ui/events/device_data_manager.h"
+#include "ui/events/ozone/device/device_event.h"
+#include "ui/events/ozone/device/device_manager.h"
+#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
+#include "ui/events/ozone/evdev/event_device_info.h"
+#include "ui/events/ozone/evdev/key_event_converter_evdev.h"
+#include "ui/events/ozone/evdev/touch_event_converter_evdev.h"
+
+#if defined(USE_EVDEV_GESTURES)
+#include "ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h"
+#include "ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h"
+#endif
+
+#ifndef EVIOCSCLOCKID
+#define EVIOCSCLOCKID  _IOW('E', 0xa0, int)
+#endif
+
+namespace ui {
+
+namespace {
+
+#if defined(USE_EVDEV_GESTURES)
+bool UseGesturesLibraryForDevice(const EventDeviceInfo& devinfo) {
+  if (devinfo.HasAbsXY() && !devinfo.IsMappedToScreen())
+    return true;  // touchpad
+
+  if (devinfo.HasRelXY())
+    return true;  // mouse
+
+  return false;
+}
+#endif
+
+scoped_ptr<EventConverterEvdev> CreateConverter(
+    int fd,
+    const base::FilePath& path,
+    int id,
+    const EventDeviceInfo& devinfo,
+    const EventDispatchCallback& dispatch,
+    EventModifiersEvdev* modifiers,
+    CursorDelegateEvdev* cursor) {
+#if defined(USE_EVDEV_GESTURES)
+  // Touchpad or mouse: use gestures library.
+  // EventReaderLibevdevCros -> GestureInterpreterLibevdevCros -> DispatchEvent
+  if (UseGesturesLibraryForDevice(devinfo)) {
+    scoped_ptr<GestureInterpreterLibevdevCros> gesture_interp = make_scoped_ptr(
+        new GestureInterpreterLibevdevCros(modifiers, cursor, dispatch));
+    scoped_ptr<EventReaderLibevdevCros> libevdev_reader =
+        make_scoped_ptr(new EventReaderLibevdevCros(
+            fd,
+            path,
+            id,
+            gesture_interp.PassAs<EventReaderLibevdevCros::Delegate>()));
+    return libevdev_reader.PassAs<EventConverterEvdev>();
+  }
+#endif
+
+  // Touchscreen: use TouchEventConverterEvdev.
+  scoped_ptr<EventConverterEvdev> converter;
+  if (devinfo.HasAbsXY())
+    return make_scoped_ptr<EventConverterEvdev>(
+        new TouchEventConverterEvdev(fd, path, id, devinfo, dispatch));
+
+  // Everything else: use KeyEventConverterEvdev.
+  return make_scoped_ptr<EventConverterEvdev>(
+      new KeyEventConverterEvdev(fd, path, id, modifiers, dispatch));
+}
+
+// Open an input device. Opening may put the calling thread to sleep, and
+// therefore should be run on a thread where latency is not critical. We
+// run it on a thread from the worker pool.
+//
+// This takes a TaskRunner and runs the reply on that thread, so that we
+// can hop threads if necessary (back to the UI thread).
+void OpenInputDevice(
+    const base::FilePath& path,
+    EventModifiersEvdev* modifiers,
+    CursorDelegateEvdev* cursor,
+    int device_id,
+    scoped_refptr<base::TaskRunner> reply_runner,
+    const EventDispatchCallback& dispatch,
+    base::Callback<void(scoped_ptr<EventConverterEvdev>)> reply_callback) {
+  TRACE_EVENT1("ozone", "OpenInputDevice", "path", path.value());
+
+  int fd = open(path.value().c_str(), O_RDONLY | O_NONBLOCK);
+  if (fd < 0) {
+    PLOG(ERROR) << "Cannot open '" << path.value();
+    return;
+  }
+
+  // Use monotonic timestamps for events. The touch code in particular
+  // expects event timestamps to correlate to the monotonic clock
+  // (base::TimeTicks).
+  unsigned int clk = CLOCK_MONOTONIC;
+  if (ioctl(fd, EVIOCSCLOCKID, &clk))
+    PLOG(ERROR) << "failed to set CLOCK_MONOTONIC";
+
+  EventDeviceInfo devinfo;
+  if (!devinfo.Initialize(fd)) {
+    LOG(ERROR) << "failed to get device information for " << path.value();
+    close(fd);
+    return;
+  }
+
+  scoped_ptr<EventConverterEvdev> converter = CreateConverter(
+      fd, path, device_id, devinfo, dispatch, modifiers, cursor);
+
+  // Reply with the constructed converter.
+  reply_runner->PostTask(FROM_HERE,
+                         base::Bind(reply_callback, base::Passed(&converter)));
+}
+
+// Close an input device. Closing may put the calling thread to sleep, and
+// therefore should be run on a thread where latency is not critical. We
+// run it on the FILE thread.
+void CloseInputDevice(const base::FilePath& path,
+                      scoped_ptr<EventConverterEvdev> converter) {
+  TRACE_EVENT1("ozone", "CloseInputDevice", "path", path.value());
+  converter.reset();
+}
+
+}  // namespace
+
+EventFactoryEvdev::EventFactoryEvdev(CursorDelegateEvdev* cursor,
+                                     DeviceManager* device_manager)
+    : last_device_id_(0),
+      device_manager_(device_manager),
+      cursor_(cursor),
+      dispatch_callback_(
+          base::Bind(base::IgnoreResult(&EventFactoryEvdev::DispatchUiEvent),
+                     base::Unretained(this))),
+      weak_ptr_factory_(this) {
+  DCHECK(device_manager_);
+}
+
+EventFactoryEvdev::~EventFactoryEvdev() { STLDeleteValues(&converters_); }
+
+void EventFactoryEvdev::DispatchUiEvent(Event* event) {
+  DispatchEvent(event);
+}
+
+void EventFactoryEvdev::AttachInputDevice(
+    const base::FilePath& path,
+    scoped_ptr<EventConverterEvdev> converter) {
+  TRACE_EVENT1("ozone", "AttachInputDevice", "path", path.value());
+  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+
+  // If we have an existing device, detach it. We don't want two
+  // devices with the same name open at the same time.
+  if (converters_[path])
+    DetachInputDevice(path);
+
+  // Add initialized device to map.
+  converters_[path] = converter.release();
+  converters_[path]->Start();
+
+  NotifyHotplugEventObserver(*converters_[path]);
+}
+
+void EventFactoryEvdev::OnDeviceEvent(const DeviceEvent& event) {
+  if (event.device_type() != DeviceEvent::INPUT)
+    return;
+
+  switch (event.action_type()) {
+    case DeviceEvent::ADD:
+    case DeviceEvent::CHANGE: {
+      TRACE_EVENT1("ozone", "OnDeviceAdded", "path", event.path().value());
+
+      // Dispatch task to open from the worker pool, since open may block.
+      base::WorkerPool::PostTask(
+          FROM_HERE,
+          base::Bind(&OpenInputDevice,
+                     event.path(),
+                     &modifiers_,
+                     cursor_,
+                     NextDeviceId(),
+                     ui_task_runner_,
+                     dispatch_callback_,
+                     base::Bind(&EventFactoryEvdev::AttachInputDevice,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                event.path())),
+          true);
+    }
+      break;
+    case DeviceEvent::REMOVE: {
+      TRACE_EVENT1("ozone", "OnDeviceRemoved", "path", event.path().value());
+      DetachInputDevice(event.path());
+    }
+      break;
+  }
+}
+
+void EventFactoryEvdev::OnDispatcherListChanged() {
+  if (!ui_task_runner_.get()) {
+    ui_task_runner_ = base::ThreadTaskRunnerHandle::Get();
+    // Scan & monitor devices.
+    device_manager_->AddObserver(this);
+    device_manager_->ScanDevices(this);
+  }
+}
+
+void EventFactoryEvdev::DetachInputDevice(const base::FilePath& path) {
+  TRACE_EVENT1("ozone", "DetachInputDevice", "path", path.value());
+  DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+
+  // Remove device from map.
+  scoped_ptr<EventConverterEvdev> converter(converters_[path]);
+  converters_.erase(path);
+
+  if (converter) {
+    // Cancel libevent notifications from this converter. This part must be
+    // on UI since the polling happens on UI.
+    converter->Stop();
+
+    NotifyHotplugEventObserver(*converter);
+
+    // Dispatch task to close from the worker pool, since close may block.
+    base::WorkerPool::PostTask(
+        FROM_HERE,
+        base::Bind(&CloseInputDevice, path, base::Passed(&converter)),
+        true);
+  }
+}
+
+void EventFactoryEvdev::WarpCursorTo(gfx::AcceleratedWidget widget,
+                                     const gfx::PointF& location) {
+  if (cursor_) {
+    cursor_->MoveCursorTo(widget, location);
+    MouseEvent mouse_event(ET_MOUSE_MOVED,
+                           cursor_->location(),
+                           cursor_->location(),
+                           modifiers_.GetModifierFlags(),
+                           /* changed_button_flags */ 0);
+    DispatchEvent(&mouse_event);
+  }
+}
+
+void EventFactoryEvdev::NotifyHotplugEventObserver(
+    const EventConverterEvdev& converter) {
+  // For now the only information propagated is related to touchscreens. Ignore
+  // events for everything but touchscreens.
+  if (!converter.HasTouchscreen())
+    return;
+
+  DeviceHotplugEventObserver* observer = DeviceDataManager::GetInstance();
+  std::vector<TouchscreenDevice> touchscreens;
+  for (auto it = converters_.begin(); it != converters_.end(); ++it) {
+    if (it->second->HasTouchscreen()) {
+      touchscreens.push_back(TouchscreenDevice(it->second->id(),
+                                               it->second->GetTouchscreenSize(),
+                                               false /* is_internal */));
+    }
+  }
+
+  observer->OnTouchscreenDevicesUpdated(touchscreens);
+}
+
+int EventFactoryEvdev::NextDeviceId() {
+  return ++last_device_id_;
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/event_factory_evdev.h b/ui/events/ozone/evdev/event_factory_evdev.h
new file mode 100644
index 0000000..15031e4
--- /dev/null
+++ b/ui/events/ozone/evdev/event_factory_evdev.h
@@ -0,0 +1,91 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_EVENT_FACTORY_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_FACTORY_EVDEV_H_
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/task_runner.h"
+#include "ui/events/ozone/device/device_event_observer.h"
+#include "ui/events/ozone/evdev/event_converter_evdev.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class PointF;
+}  // namespace gfx
+
+namespace ui {
+
+class CursorDelegateEvdev;
+class DeviceManager;
+
+// Ozone events implementation for the Linux input subsystem ("evdev").
+class EVENTS_OZONE_EVDEV_EXPORT EventFactoryEvdev : public DeviceEventObserver,
+                                                    public PlatformEventSource {
+ public:
+  EventFactoryEvdev(CursorDelegateEvdev* cursor,
+                    DeviceManager* device_manager);
+  virtual ~EventFactoryEvdev();
+
+  void DispatchUiEvent(Event* event);
+
+  void WarpCursorTo(gfx::AcceleratedWidget widget,
+                    const gfx::PointF& location);
+
+ private:
+  // Open device at path & starting processing events (on UI thread).
+  void AttachInputDevice(const base::FilePath& file_path,
+                         scoped_ptr<EventConverterEvdev> converter);
+
+  // Close device at path (on UI thread).
+  void DetachInputDevice(const base::FilePath& file_path);
+
+  void NotifyHotplugEventObserver(const EventConverterEvdev& converter);
+
+  int NextDeviceId();
+
+  // DeviceEventObserver overrides:
+  //
+  // Callback for device add (on UI thread).
+  virtual void OnDeviceEvent(const DeviceEvent& event) OVERRIDE;
+
+  // PlatformEventSource:
+  virtual void OnDispatcherListChanged() OVERRIDE;
+
+  // Owned per-device event converters (by path).
+  std::map<base::FilePath, EventConverterEvdev*> converters_;
+
+  // Used to uniquely identify input devices.
+  int last_device_id_;
+
+  // Interface for scanning & monitoring input devices.
+  DeviceManager* device_manager_;  // Not owned.
+
+  // Task runner for event dispatch.
+  scoped_refptr<base::TaskRunner> ui_task_runner_;
+
+  // Modifier key state (shift, ctrl, etc).
+  EventModifiersEvdev modifiers_;
+
+  // Cursor movement.
+  CursorDelegateEvdev* cursor_;
+
+  // Dispatch callback for events.
+  EventDispatchCallback dispatch_callback_;
+
+  // Support weak pointers for attach & detach callbacks.
+  base::WeakPtrFactory<EventFactoryEvdev> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventFactoryEvdev);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_EVENT_FACTORY_EVDEV_H_
diff --git a/ui/events/ozone/evdev/event_modifiers_evdev.cc b/ui/events/ozone/evdev/event_modifiers_evdev.cc
new file mode 100644
index 0000000..cee7c1c
--- /dev/null
+++ b/ui/events/ozone/evdev/event_modifiers_evdev.cc
@@ -0,0 +1,79 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/event_modifiers_evdev.h"
+
+#include <linux/input.h>
+
+#include "ui/events/event.h"
+
+namespace ui {
+
+namespace {
+
+static const int kEventFlagFromModifiers[] = {
+    EF_NONE,                 // EVDEV_MODIFIER_NONE,
+    EF_CAPS_LOCK_DOWN,       // EVDEV_MODIFIER_CAPS_LOCK
+    EF_SHIFT_DOWN,           // EVDEV_MODIFIER_SHIFT
+    EF_CONTROL_DOWN,         // EVDEV_MODIFIER_CONTROL
+    EF_ALT_DOWN,             // EVDEV_MODIFIER_ALT
+    EF_LEFT_MOUSE_BUTTON,    // EVDEV_MODIFIER_LEFT_MOUSE_BUTTON
+    EF_MIDDLE_MOUSE_BUTTON,  // EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON
+    EF_RIGHT_MOUSE_BUTTON,   // EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON
+    EF_COMMAND_DOWN,         // EVDEV_MODIFIER_COMMAND
+    EF_ALTGR_DOWN,           // EVDEV_MODIFIER_ALTGR
+};
+
+}  // namespace
+
+EventModifiersEvdev::EventModifiersEvdev()
+    : modifier_flags_locked_(0), modifier_flags_(0) {
+  memset(modifiers_down_, 0, sizeof(modifiers_down_));
+}
+EventModifiersEvdev::~EventModifiersEvdev() {}
+
+void EventModifiersEvdev::UpdateModifier(unsigned int modifier, bool down) {
+  DCHECK_LT(modifier, EVDEV_NUM_MODIFIERS);
+
+  if (down) {
+    modifiers_down_[modifier]++;
+  } else {
+    // Ignore spurious modifier "up" events. This might happen if the
+    // button is down during startup.
+    if (modifiers_down_[modifier])
+      modifiers_down_[modifier]--;
+  }
+
+  UpdateFlags(modifier);
+}
+
+void EventModifiersEvdev::UpdateModifierLock(unsigned int modifier, bool down) {
+  DCHECK_LT(modifier, EVDEV_NUM_MODIFIERS);
+
+  if (down)
+    modifier_flags_locked_ ^= kEventFlagFromModifiers[modifier];
+
+  // TODO(spang): Synchronize with the CapsLock LED.
+
+  UpdateFlags(modifier);
+}
+
+void EventModifiersEvdev::UpdateFlags(unsigned int modifier) {
+  int mask = kEventFlagFromModifiers[modifier];
+  bool down = modifiers_down_[modifier];
+  bool locked = (modifier_flags_locked_ & mask);
+  if (down != locked)
+    modifier_flags_ |= mask;
+  else
+    modifier_flags_ &= ~mask;
+}
+
+int EventModifiersEvdev::GetModifierFlags() { return modifier_flags_; }
+
+// static
+int EventModifiersEvdev::GetEventFlagFromModifier(unsigned int modifier) {
+  return kEventFlagFromModifiers[modifier];
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/event_modifiers_evdev.h b/ui/events/ozone/evdev/event_modifiers_evdev.h
new file mode 100644
index 0000000..d10bb8c
--- /dev/null
+++ b/ui/events/ozone/evdev/event_modifiers_evdev.h
@@ -0,0 +1,77 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
+
+#include "base/basictypes.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+
+namespace ui {
+
+enum {
+  EVDEV_MODIFIER_NONE,
+  EVDEV_MODIFIER_CAPS_LOCK,
+  EVDEV_MODIFIER_SHIFT,
+  EVDEV_MODIFIER_CONTROL,
+  EVDEV_MODIFIER_ALT,
+  EVDEV_MODIFIER_LEFT_MOUSE_BUTTON,
+  EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON,
+  EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON,
+  EVDEV_MODIFIER_COMMAND,
+  EVDEV_MODIFIER_ALTGR,
+  EVDEV_NUM_MODIFIERS
+};
+
+// Modifier key state for Evdev.
+//
+// Chrome relies on the underlying OS to interpret modifier keys such as Shift,
+// Ctrl, and Alt. The Linux input subsystem does not assign any special meaning
+// to these keys, so this work must happen at a higher layer (normally X11 or
+// the console driver). When using evdev directly, we must do it ourselves.
+//
+// The modifier state is shared between all input devices connected to the
+// system. This is to support actions such as Shift-Clicking that use multiple
+// devices.
+//
+// Normally a modifier is set if any of the keys or buttons assigned to it are
+// currently pressed. However some keys toggle a persistent "lock" for the
+// modifier instead, such as CapsLock. If a modifier is "locked" then its state
+// is inverted until it is unlocked.
+class EVENTS_OZONE_EVDEV_EXPORT EventModifiersEvdev {
+ public:
+  EventModifiersEvdev();
+  ~EventModifiersEvdev();
+
+  // Record key press or release for regular modifier key (shift, alt, etc).
+  void UpdateModifier(unsigned int modifier, bool down);
+
+  // Record key press or release for locking modifier key (caps lock).
+  void UpdateModifierLock(unsigned int modifier, bool down);
+
+  // Return current flags to use for incoming events.
+  int GetModifierFlags();
+
+  // Return the mask for the specified modifier.
+  static int GetEventFlagFromModifier(unsigned int modifier);
+
+ private:
+  // Count of keys pressed for each modifier.
+  int modifiers_down_[EVDEV_NUM_MODIFIERS];
+
+  // Mask of modifier flags currently "locked".
+  int modifier_flags_locked_;
+
+  // Mask of modifier flags currently active (nonzero keys pressed xor locked).
+  int modifier_flags_;
+
+  // Update modifier_flags_ from modifiers_down_ and modifier_flags_locked_.
+  void UpdateFlags(unsigned int modifier);
+
+  DISALLOW_COPY_AND_ASSIGN(EventModifiersEvdev);
+};
+
+}  // namspace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
diff --git a/ui/events/ozone/evdev/events_ozone_evdev_export.h b/ui/events/ozone/evdev/events_ozone_evdev_export.h
new file mode 100644
index 0000000..4a4cbac
--- /dev/null
+++ b/ui/events/ozone/evdev/events_ozone_evdev_export.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_EVENTS_OZONE_EVDEV_EXPORT_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENTS_OZONE_EVDEV_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(EVENTS_OZONE_EVDEV_IMPLEMENTATION)
+#define EVENTS_OZONE_EVDEV_EXPORT __declspec(dllexport)
+#else
+#define EVENTS_OZONE_EVDEV_EXPORT __declspec(dllimport)
+#endif  // defined(EVENTS_OZONE_EVDEV_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(EVENTS_OZONE_EVDEV_IMPLEMENTATION)
+#define EVENTS_OZONE_EVDEV_EXPORT __attribute__((visibility("default")))
+#else
+#define EVENTS_OZONE_EVDEV_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define EVENTS_OZONE_EVDEV_EXPORT
+#endif
+
+#endif  // UI_EVENTS_OZONE_EVDEV_EVENTS_OZONE_EVDEV_EXPORT_H_
diff --git a/ui/events/ozone/evdev/key_event_converter_evdev.cc b/ui/events/ozone/evdev/key_event_converter_evdev.cc
new file mode 100644
index 0000000..195b514
--- /dev/null
+++ b/ui/events/ozone/evdev/key_event_converter_evdev.cc
@@ -0,0 +1,263 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/key_event_converter_evdev.h"
+
+#include <errno.h>
+#include <linux/input.h>
+
+#include "base/message_loop/message_loop.h"
+#include "ui/events/event.h"
+#include "ui/events/keycodes/dom4/keycode_converter.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
+
+namespace ui {
+
+namespace {
+
+const int kXkbKeycodeOffset = 8;
+
+ui::KeyboardCode KeyboardCodeFromButton(unsigned int code) {
+  static const ui::KeyboardCode kLinuxBaseKeyMap[] = {
+      ui::VKEY_UNKNOWN,            // KEY_RESERVED
+      ui::VKEY_ESCAPE,             // KEY_ESC
+      ui::VKEY_1,                  // KEY_1
+      ui::VKEY_2,                  // KEY_2
+      ui::VKEY_3,                  // KEY_3
+      ui::VKEY_4,                  // KEY_4
+      ui::VKEY_5,                  // KEY_5
+      ui::VKEY_6,                  // KEY_6
+      ui::VKEY_7,                  // KEY_7
+      ui::VKEY_8,                  // KEY_8
+      ui::VKEY_9,                  // KEY_9
+      ui::VKEY_0,                  // KEY_0
+      ui::VKEY_OEM_MINUS,          // KEY_MINUS
+      ui::VKEY_OEM_PLUS,           // KEY_EQUAL
+      ui::VKEY_BACK,               // KEY_BACKSPACE
+      ui::VKEY_TAB,                // KEY_TAB
+      ui::VKEY_Q,                  // KEY_Q
+      ui::VKEY_W,                  // KEY_W
+      ui::VKEY_E,                  // KEY_E
+      ui::VKEY_R,                  // KEY_R
+      ui::VKEY_T,                  // KEY_T
+      ui::VKEY_Y,                  // KEY_Y
+      ui::VKEY_U,                  // KEY_U
+      ui::VKEY_I,                  // KEY_I
+      ui::VKEY_O,                  // KEY_O
+      ui::VKEY_P,                  // KEY_P
+      ui::VKEY_OEM_4,              // KEY_LEFTBRACE
+      ui::VKEY_OEM_6,              // KEY_RIGHTBRACE
+      ui::VKEY_RETURN,             // KEY_ENTER
+      ui::VKEY_CONTROL,            // KEY_LEFTCTRL
+      ui::VKEY_A,                  // KEY_A
+      ui::VKEY_S,                  // KEY_S
+      ui::VKEY_D,                  // KEY_D
+      ui::VKEY_F,                  // KEY_F
+      ui::VKEY_G,                  // KEY_G
+      ui::VKEY_H,                  // KEY_H
+      ui::VKEY_J,                  // KEY_J
+      ui::VKEY_K,                  // KEY_K
+      ui::VKEY_L,                  // KEY_L
+      ui::VKEY_OEM_1,              // KEY_SEMICOLON
+      ui::VKEY_OEM_7,              // KEY_APOSTROPHE
+      ui::VKEY_OEM_3,              // KEY_GRAVE
+      ui::VKEY_SHIFT,              // KEY_LEFTSHIFT
+      ui::VKEY_OEM_5,              // KEY_BACKSLASH
+      ui::VKEY_Z,                  // KEY_Z
+      ui::VKEY_X,                  // KEY_X
+      ui::VKEY_C,                  // KEY_C
+      ui::VKEY_V,                  // KEY_V
+      ui::VKEY_B,                  // KEY_B
+      ui::VKEY_N,                  // KEY_N
+      ui::VKEY_M,                  // KEY_M
+      ui::VKEY_OEM_COMMA,          // KEY_COMMA
+      ui::VKEY_OEM_PERIOD,         // KEY_DOT
+      ui::VKEY_OEM_2,              // KEY_SLASH
+      ui::VKEY_SHIFT,              // KEY_RIGHTSHIFT
+      ui::VKEY_MULTIPLY,           // KEY_KPASTERISK
+      ui::VKEY_MENU,               // KEY_LEFTALT
+      ui::VKEY_SPACE,              // KEY_SPACE
+      ui::VKEY_CAPITAL,            // KEY_CAPSLOCK
+      ui::VKEY_F1,                 // KEY_F1
+      ui::VKEY_F2,                 // KEY_F2
+      ui::VKEY_F3,                 // KEY_F3
+      ui::VKEY_F4,                 // KEY_F4
+      ui::VKEY_F5,                 // KEY_F5
+      ui::VKEY_F6,                 // KEY_F6
+      ui::VKEY_F7,                 // KEY_F7
+      ui::VKEY_F8,                 // KEY_F8
+      ui::VKEY_F9,                 // KEY_F9
+      ui::VKEY_F10,                // KEY_F10
+      ui::VKEY_NUMLOCK,            // KEY_NUMLOCK
+      ui::VKEY_SCROLL,             // KEY_SCROLLLOCK
+      ui::VKEY_NUMPAD7,            // KEY_KP7
+      ui::VKEY_NUMPAD8,            // KEY_KP8
+      ui::VKEY_NUMPAD9,            // KEY_KP9
+      ui::VKEY_SUBTRACT,           // KEY_KPMINUS
+      ui::VKEY_NUMPAD4,            // KEY_KP4
+      ui::VKEY_NUMPAD5,            // KEY_KP5
+      ui::VKEY_NUMPAD6,            // KEY_KP6
+      ui::VKEY_ADD,                // KEY_KPPLUS
+      ui::VKEY_NUMPAD1,            // KEY_KP1
+      ui::VKEY_NUMPAD2,            // KEY_KP2
+      ui::VKEY_NUMPAD3,            // KEY_KP3
+      ui::VKEY_NUMPAD0,            // KEY_KP0
+      ui::VKEY_DECIMAL,            // KEY_KPDOT
+      ui::VKEY_UNKNOWN,            // (unassigned)
+      ui::VKEY_DBE_DBCSCHAR,       // KEY_ZENKAKUHANKAKU
+      ui::VKEY_OEM_102,            // KEY_102ND
+      ui::VKEY_F11,                // KEY_F11
+      ui::VKEY_F12,                // KEY_F12
+      ui::VKEY_UNKNOWN,            // KEY_RO
+      ui::VKEY_UNKNOWN,            // KEY_KATAKANA
+      ui::VKEY_UNKNOWN,            // KEY_HIRAGANA
+      ui::VKEY_CONVERT,            // KEY_HENKAN
+      ui::VKEY_UNKNOWN,            // KEY_KATAKANAHIRAGANA
+      ui::VKEY_NONCONVERT,         // KEY_MUHENKAN
+      ui::VKEY_UNKNOWN,            // KEY_KPJPCOMMA
+      ui::VKEY_RETURN,             // KEY_KPENTER
+      ui::VKEY_CONTROL,            // KEY_RIGHTCTRL
+      ui::VKEY_DIVIDE,             // KEY_KPSLASH
+      ui::VKEY_PRINT,              // KEY_SYSRQ
+      ui::VKEY_MENU,               // KEY_RIGHTALT
+      ui::VKEY_RETURN,             // KEY_LINEFEED
+      ui::VKEY_HOME,               // KEY_HOME
+      ui::VKEY_UP,                 // KEY_UP
+      ui::VKEY_PRIOR,              // KEY_PAGEUP
+      ui::VKEY_LEFT,               // KEY_LEFT
+      ui::VKEY_RIGHT,              // KEY_RIGHT
+      ui::VKEY_END,                // KEY_END
+      ui::VKEY_DOWN,               // KEY_DOWN
+      ui::VKEY_NEXT,               // KEY_PAGEDOWN
+      ui::VKEY_INSERT,             // KEY_INSERT
+      ui::VKEY_DELETE,             // KEY_DELETE
+      ui::VKEY_UNKNOWN,            // KEY_MACRO
+      ui::VKEY_VOLUME_MUTE,        // KEY_MUTE
+      ui::VKEY_VOLUME_DOWN,        // KEY_VOLUMEDOWN
+      ui::VKEY_VOLUME_UP,          // KEY_VOLUMEUP
+      ui::VKEY_POWER,              // KEY_POWER
+      ui::VKEY_OEM_PLUS,           // KEY_KPEQUAL
+      ui::VKEY_UNKNOWN,            // KEY_KPPLUSMINUS
+      ui::VKEY_PAUSE,              // KEY_PAUSE
+      ui::VKEY_MEDIA_LAUNCH_APP1,  // KEY_SCALE
+      ui::VKEY_DECIMAL,            // KEY_KPCOMMA
+      ui::VKEY_HANGUL,             // KEY_HANGEUL
+      ui::VKEY_HANJA,              // KEY_HANJA
+      ui::VKEY_UNKNOWN,            // KEY_YEN
+      ui::VKEY_LWIN,               // KEY_LEFTMETA
+      ui::VKEY_RWIN,               // KEY_RIGHTMETA
+      ui::VKEY_APPS,               // KEY_COMPOSE
+  };
+
+  if (code < arraysize(kLinuxBaseKeyMap))
+    return kLinuxBaseKeyMap[code];
+
+  LOG(ERROR) << "Unknown key code: " << code;
+  return ui::VKEY_UNKNOWN;
+}
+
+int ModifierFromButton(unsigned int code) {
+  switch (code) {
+    case KEY_CAPSLOCK:
+      return EVDEV_MODIFIER_CAPS_LOCK;
+    case KEY_LEFTSHIFT:
+    case KEY_RIGHTSHIFT:
+      return EVDEV_MODIFIER_SHIFT;
+    case KEY_LEFTCTRL:
+    case KEY_RIGHTCTRL:
+      return EVDEV_MODIFIER_CONTROL;
+    case KEY_LEFTALT:
+    case KEY_RIGHTALT:
+      return EVDEV_MODIFIER_ALT;
+    case BTN_LEFT:
+      return EVDEV_MODIFIER_LEFT_MOUSE_BUTTON;
+    case BTN_MIDDLE:
+      return EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON;
+    case BTN_RIGHT:
+      return EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON;
+    case KEY_LEFTMETA:
+    case KEY_RIGHTMETA:
+      return EVDEV_MODIFIER_COMMAND;
+    default:
+      return EVDEV_MODIFIER_NONE;
+  }
+}
+
+bool IsLockButton(unsigned int code) { return code == KEY_CAPSLOCK; }
+
+}  // namespace
+
+KeyEventConverterEvdev::KeyEventConverterEvdev(
+    int fd,
+    base::FilePath path,
+    int id,
+    EventModifiersEvdev* modifiers,
+    const EventDispatchCallback& callback)
+    : EventConverterEvdev(fd, path, id),
+      callback_(callback),
+      modifiers_(modifiers) {
+  // TODO(spang): Initialize modifiers using EVIOCGKEY.
+}
+
+KeyEventConverterEvdev::~KeyEventConverterEvdev() {
+  Stop();
+  close(fd_);
+}
+
+void KeyEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) {
+  input_event inputs[4];
+  ssize_t read_size = read(fd, inputs, sizeof(inputs));
+  if (read_size < 0) {
+    if (errno == EINTR || errno == EAGAIN)
+      return;
+    if (errno != ENODEV)
+      PLOG(ERROR) << "error reading device " << path_.value();
+    Stop();
+    return;
+  }
+
+  DCHECK_EQ(read_size % sizeof(*inputs), 0u);
+  ProcessEvents(inputs, read_size / sizeof(*inputs));
+}
+
+void KeyEventConverterEvdev::ProcessEvents(const input_event* inputs,
+                                           int count) {
+  for (int i = 0; i < count; ++i) {
+    const input_event& input = inputs[i];
+    if (input.type == EV_KEY) {
+      ConvertKeyEvent(input.code, input.value);
+    } else if (input.type == EV_SYN) {
+      // TODO(sadrul): Handle this case appropriately.
+    }
+  }
+}
+
+void KeyEventConverterEvdev::ConvertKeyEvent(int key, int value) {
+  int down = (value != 0);
+  int repeat = (value == 2);
+  int modifier = ModifierFromButton(key);
+  ui::KeyboardCode code = KeyboardCodeFromButton(key);
+
+  if (!repeat && (modifier != EVDEV_MODIFIER_NONE)) {
+    if (IsLockButton(key)) {
+      // Locking modifier keys: CapsLock.
+      modifiers_->UpdateModifierLock(modifier, down);
+    } else {
+      // Regular modifier keys: Shift, Ctrl, Alt, etc.
+      modifiers_->UpdateModifier(modifier, down);
+    }
+  }
+
+  int flags = modifiers_->GetModifierFlags();
+
+  KeyEvent key_event(
+      down ? ET_KEY_PRESSED : ET_KEY_RELEASED,
+      code,
+      KeycodeConverter::NativeKeycodeToCode(key + kXkbKeycodeOffset),
+      flags);
+  callback_.Run(&key_event);
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/key_event_converter_evdev.h b/ui/events/ozone/evdev/key_event_converter_evdev.h
new file mode 100644
index 0000000..d1cab8c
--- /dev/null
+++ b/ui/events/ozone/evdev/key_event_converter_evdev.h
@@ -0,0 +1,52 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_
+
+#include "base/files/file_path.h"
+#include "base/message_loop/message_pump_libevent.h"
+#include "ui/events/event.h"
+#include "ui/events/ozone/evdev/event_converter_evdev.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+
+struct input_event;
+
+namespace ui {
+
+class EVENTS_OZONE_EVDEV_EXPORT KeyEventConverterEvdev
+    : public EventConverterEvdev {
+ public:
+  KeyEventConverterEvdev(int fd,
+                         base::FilePath path,
+                         int id,
+                         EventModifiersEvdev* modifiers,
+                         const EventDispatchCallback& dispatch);
+  virtual ~KeyEventConverterEvdev();
+
+  // EventConverterEvdev:
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+
+  void ProcessEvents(const struct input_event* inputs, int count);
+
+ private:
+  // Callback for dispatching events.
+  EventDispatchCallback callback_;
+
+  // Shared modifier state.
+  EventModifiersEvdev* modifiers_;
+
+  // Controller for watching the input fd.
+  base::MessagePumpLibevent::FileDescriptorWatcher controller_;
+
+  void ConvertKeyEvent(int key, int value);
+
+  DISALLOW_COPY_AND_ASSIGN(KeyEventConverterEvdev);
+};
+
+}  // namspace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_
+
diff --git a/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc
new file mode 100644
index 0000000..575e86d
--- /dev/null
+++ b/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc
@@ -0,0 +1,351 @@
+// Copyright 2014 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 <linux/input.h>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/ozone/evdev/key_event_converter_evdev.h"
+
+namespace ui {
+
+const char kTestDevicePath[] = "/dev/input/test-device";
+
+class MockKeyEventConverterEvdev : public KeyEventConverterEvdev {
+ public:
+  MockKeyEventConverterEvdev(int fd, EventModifiersEvdev* modifiers)
+      : KeyEventConverterEvdev(
+            fd,
+            base::FilePath(kTestDevicePath),
+            1,
+            modifiers,
+            base::Bind(&MockKeyEventConverterEvdev::DispatchEventForTest,
+                       base::Unretained(this))) {
+    Start();
+  }
+  virtual ~MockKeyEventConverterEvdev() {};
+
+  unsigned size() { return dispatched_events_.size(); }
+  KeyEvent* event(unsigned index) {
+    DCHECK_GT(dispatched_events_.size(), index);
+    return dispatched_events_[index];
+  }
+
+  void DispatchEventForTest(Event* event);
+
+ private:
+  ScopedVector<KeyEvent> dispatched_events_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockKeyEventConverterEvdev);
+};
+
+void MockKeyEventConverterEvdev::DispatchEventForTest(Event* event) {
+  dispatched_events_.push_back(new KeyEvent(*static_cast<KeyEvent*>(event)));
+}
+
+}  // namespace ui
+
+// Test fixture.
+class KeyEventConverterEvdevTest : public testing::Test {
+ public:
+  KeyEventConverterEvdevTest() {}
+
+  // Overridden from testing::Test:
+  virtual void SetUp() OVERRIDE {
+
+    // Set up pipe to satisfy message pump (unused).
+    int evdev_io[2];
+    if (pipe(evdev_io))
+      PLOG(FATAL) << "failed pipe";
+    events_in_ = evdev_io[0];
+    events_out_ = evdev_io[1];
+
+    modifiers_ = new ui::EventModifiersEvdev();
+    device_ = new ui::MockKeyEventConverterEvdev(events_in_, modifiers_);
+  }
+  virtual void TearDown() OVERRIDE {
+    delete device_;
+    delete modifiers_;
+    close(events_in_);
+    close(events_out_);
+  }
+
+  ui::MockKeyEventConverterEvdev* device() { return device_; }
+  ui::EventModifiersEvdev* modifiers() { return modifiers_; }
+
+ private:
+  base::MessageLoopForUI ui_loop_;
+
+  ui::EventModifiersEvdev* modifiers_;
+  ui::MockKeyEventConverterEvdev* device_;
+
+  int events_out_;
+  int events_in_;
+
+  DISALLOW_COPY_AND_ASSIGN(KeyEventConverterEvdevTest);
+};
+
+TEST_F(KeyEventConverterEvdevTest, KeyPress) {
+  ui::MockKeyEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+      {{0, 0}, EV_KEY, KEY_BACKSPACE, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+      {{0, 0}, EV_KEY, KEY_BACKSPACE, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+  EXPECT_EQ(2u, dev->size());
+
+  ui::KeyEvent* event;
+
+  event = dev->event(0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+  EXPECT_EQ(0, event->flags());
+
+  event = dev->event(1);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+  EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyRepeat) {
+  ui::MockKeyEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+      {{0, 0}, EV_KEY, KEY_BACKSPACE, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+      {{0, 0}, EV_KEY, KEY_BACKSPACE, 2},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+      {{0, 0}, EV_KEY, KEY_BACKSPACE, 2},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7002a},
+      {{0, 0}, EV_KEY, KEY_BACKSPACE, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+  EXPECT_EQ(4u, dev->size());
+
+  ui::KeyEvent* event;
+
+  event = dev->event(0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+  EXPECT_EQ(0, event->flags());
+
+  event = dev->event(1);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+  EXPECT_EQ(0, event->flags());
+
+  event = dev->event(2);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+  EXPECT_EQ(0, event->flags());
+
+  event = dev->event(3);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_BACK, event->key_code());
+  EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, NoEvents) {
+  ui::MockKeyEventConverterEvdev* dev = device();
+  dev->ProcessEvents(NULL, 0);
+  EXPECT_EQ(0u, dev->size());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyWithModifier) {
+  ui::MockKeyEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+      {{0, 0}, EV_KEY, KEY_LEFTSHIFT, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70004},
+      {{0, 0}, EV_KEY, KEY_A, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70004},
+      {{0, 0}, EV_KEY, KEY_A, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+      {{0, 0}, EV_KEY, KEY_LEFTSHIFT, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+  EXPECT_EQ(4u, dev->size());
+
+  ui::KeyEvent* event;
+
+  event = dev->event(0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_SHIFT, event->key_code());
+  EXPECT_EQ(ui::EF_SHIFT_DOWN, event->flags());
+
+  event = dev->event(1);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_A, event->key_code());
+  EXPECT_EQ(ui::EF_SHIFT_DOWN, event->flags());
+
+  event = dev->event(2);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_A, event->key_code());
+  EXPECT_EQ(ui::EF_SHIFT_DOWN, event->flags());
+
+  event = dev->event(3);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_SHIFT, event->key_code());
+  EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyWithDuplicateModifier) {
+  ui::MockKeyEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+      {{0, 0}, EV_KEY, KEY_LEFTCTRL, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x700e5},
+      {{0, 0}, EV_KEY, KEY_RIGHTCTRL, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7001d},
+      {{0, 0}, EV_KEY, KEY_Z, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x7001d},
+      {{0, 0}, EV_KEY, KEY_Z, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x700e1},
+      {{0, 0}, EV_KEY, KEY_LEFTCTRL, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x700e5},
+      {{0, 0}, EV_KEY, KEY_RIGHTCTRL, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+  EXPECT_EQ(6u, dev->size());
+
+  ui::KeyEvent* event;
+
+  event = dev->event(0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+  EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+  event = dev->event(1);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+  EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+  event = dev->event(2);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_Z, event->key_code());
+  EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+  event = dev->event(3);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_Z, event->key_code());
+  EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+  event = dev->event(4);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+  EXPECT_EQ(ui::EF_CONTROL_DOWN, event->flags());
+
+  event = dev->event(5);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_CONTROL, event->key_code());
+  EXPECT_EQ(0, event->flags());
+}
+
+TEST_F(KeyEventConverterEvdevTest, KeyWithLock) {
+  ui::MockKeyEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+      {{0, 0}, EV_KEY, KEY_CAPSLOCK, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+      {{0, 0}, EV_KEY, KEY_CAPSLOCK, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70014},
+      {{0, 0}, EV_KEY, KEY_Q, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70014},
+      {{0, 0}, EV_KEY, KEY_Q, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+      {{0, 0}, EV_KEY, KEY_CAPSLOCK, 1},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+
+      {{0, 0}, EV_MSC, MSC_SCAN, 0x70039},
+      {{0, 0}, EV_KEY, KEY_CAPSLOCK, 0},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  dev->ProcessEvents(mock_kernel_queue, arraysize(mock_kernel_queue));
+  EXPECT_EQ(6u, dev->size());
+
+  ui::KeyEvent* event;
+
+  event = dev->event(0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+  EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+  event = dev->event(1);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+  EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+  event = dev->event(2);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_Q, event->key_code());
+  EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+  event = dev->event(3);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_Q, event->key_code());
+  EXPECT_EQ(ui::EF_CAPS_LOCK_DOWN, event->flags());
+
+  event = dev->event(4);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, event->type());
+  EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+  EXPECT_EQ(0, event->flags());
+
+  event = dev->event(5);
+  EXPECT_EQ(ui::ET_KEY_RELEASED, event->type());
+  EXPECT_EQ(ui::VKEY_CAPITAL, event->key_code());
+  EXPECT_EQ(0, event->flags());
+}
diff --git a/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc
new file mode 100644
index 0000000..f7859cf
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc
@@ -0,0 +1,91 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h"
+
+#include <errno.h>
+#include <libevdev/libevdev.h>
+#include <linux/input.h>
+
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+
+namespace ui {
+
+namespace {
+
+std::string FormatLog(const char* fmt, va_list args) {
+  std::string msg = base::StringPrintV(fmt, args);
+  if (!msg.empty() && msg[msg.size() - 1] == '\n')
+    msg.erase(msg.end() - 1, msg.end());
+  return msg;
+}
+
+}  // namespace
+
+EventReaderLibevdevCros::EventReaderLibevdevCros(int fd,
+                                                 const base::FilePath& path,
+                                                 int id,
+                                                 scoped_ptr<Delegate> delegate)
+    : EventConverterEvdev(fd, path, id), delegate_(delegate.Pass()) {
+  memset(&evdev_, 0, sizeof(evdev_));
+  evdev_.log = OnLogMessage;
+  evdev_.log_udata = this;
+  evdev_.syn_report = OnSynReport;
+  evdev_.syn_report_udata = this;
+  evdev_.fd = fd;
+
+  memset(&evstate_, 0, sizeof(evstate_));
+  evdev_.evstate = &evstate_;
+  Event_Init(&evdev_);
+
+  Event_Open(&evdev_);
+
+  delegate_->OnLibEvdevCrosOpen(&evdev_, &evstate_);
+}
+
+EventReaderLibevdevCros::~EventReaderLibevdevCros() {
+  Stop();
+  EvdevClose(&evdev_);
+}
+
+EventReaderLibevdevCros::Delegate::~Delegate() {}
+
+void EventReaderLibevdevCros::OnFileCanReadWithoutBlocking(int fd) {
+  if (EvdevRead(&evdev_)) {
+    if (errno == EINTR || errno == EAGAIN)
+      return;
+    if (errno != ENODEV)
+      PLOG(ERROR) << "error reading device " << path_.value();
+    Stop();
+    return;
+  }
+}
+
+// static
+void EventReaderLibevdevCros::OnSynReport(void* data,
+                                          EventStateRec* evstate,
+                                          struct timeval* tv) {
+  EventReaderLibevdevCros* reader = static_cast<EventReaderLibevdevCros*>(data);
+  reader->delegate_->OnLibEvdevCrosEvent(&reader->evdev_, evstate, *tv);
+}
+
+// static
+void EventReaderLibevdevCros::OnLogMessage(void* data,
+                                           int level,
+                                           const char* fmt,
+                                           ...) {
+  va_list args;
+  va_start(args, fmt);
+  if (level >= LOGLEVEL_ERROR)
+    LOG(ERROR) << "libevdev: " << FormatLog(fmt, args);
+  else if (level >= LOGLEVEL_WARNING)
+    LOG(WARNING) << "libevdev: " << FormatLog(fmt, args);
+  else
+    VLOG(3) << "libevdev: " << FormatLog(fmt, args);
+  va_end(args);
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h
new file mode 100644
index 0000000..a67d037
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h
@@ -0,0 +1,70 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_
+#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_
+
+#include <libevdev/libevdev.h>
+
+#include "base/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/events/ozone/evdev/event_converter_evdev.h"
+
+namespace ui {
+
+// Basic wrapper for libevdev-cros.
+//
+// This drives libevdev-cros from a file descriptor and calls delegate
+// with the updated event state from libevdev-cros.
+//
+// The library doesn't support all devices currently. In particular there
+// is no support for keyboard events.
+class EventReaderLibevdevCros : public EventConverterEvdev {
+ public:
+  class Delegate {
+   public:
+    virtual ~Delegate();
+
+    // Notifier for open. This is called with the initial event state.
+    virtual void OnLibEvdevCrosOpen(Evdev* evdev, EventStateRec* evstate) = 0;
+
+    // Notifier for event. This is called with the updated event state.
+    virtual void OnLibEvdevCrosEvent(Evdev* evdev,
+                                     EventStateRec* state,
+                                     const timeval& time) = 0;
+  };
+
+  EventReaderLibevdevCros(int fd,
+                          const base::FilePath& path,
+                          int id,
+                          scoped_ptr<Delegate> delegate);
+  ~EventReaderLibevdevCros();
+
+  // EventConverterEvdev:
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+
+ private:
+  static void OnSynReport(void* data,
+                          EventStateRec* evstate,
+                          struct timeval* tv);
+  static void OnLogMessage(void*, int level, const char*, ...);
+
+  // Libevdev state.
+  Evdev evdev_;
+
+  // Event state.
+  EventStateRec evstate_;
+
+  // Path to input device.
+  base::FilePath path_;
+
+  // Delegate for event processing.
+  scoped_ptr<Delegate> delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventReaderLibevdevCros);
+};
+
+}  // namspace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
new file mode 100644
index 0000000..59d8735
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
@@ -0,0 +1,398 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h"
+
+#include <gestures/gestures.h>
+#include <libevdev/libevdev.h>
+
+#include "base/strings/stringprintf.h"
+#include "base/timer/timer.h"
+#include "ui/events/event.h"
+#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
+#include "ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace ui {
+
+namespace {
+
+// Convert libevdev device class to libgestures device class.
+GestureInterpreterDeviceClass GestureDeviceClass(Evdev* evdev) {
+  switch (evdev->info.evdev_class) {
+    case EvdevClassMouse:
+      return GESTURES_DEVCLASS_MOUSE;
+    case EvdevClassMultitouchMouse:
+      return GESTURES_DEVCLASS_MULTITOUCH_MOUSE;
+    case EvdevClassTouchpad:
+      return GESTURES_DEVCLASS_TOUCHPAD;
+    case EvdevClassTouchscreen:
+      return GESTURES_DEVCLASS_TOUCHSCREEN;
+    default:
+      return GESTURES_DEVCLASS_UNKNOWN;
+  }
+}
+
+// Convert libevdev state to libgestures hardware properties.
+HardwareProperties GestureHardwareProperties(Evdev* evdev) {
+  HardwareProperties hwprops;
+  hwprops.left = Event_Get_Left(evdev);
+  hwprops.top = Event_Get_Top(evdev);
+  hwprops.right = Event_Get_Right(evdev);
+  hwprops.bottom = Event_Get_Bottom(evdev);
+  hwprops.res_x = Event_Get_Res_X(evdev);
+  hwprops.res_y = Event_Get_Res_Y(evdev);
+  hwprops.screen_x_dpi = 133;
+  hwprops.screen_y_dpi = 133;
+  hwprops.orientation_minimum = Event_Get_Orientation_Minimum(evdev);
+  hwprops.orientation_maximum = Event_Get_Orientation_Maximum(evdev);
+  hwprops.max_finger_cnt = Event_Get_Slot_Count(evdev);
+  hwprops.max_touch_cnt = Event_Get_Touch_Count_Max(evdev);
+  hwprops.supports_t5r2 = Event_Get_T5R2(evdev);
+  hwprops.support_semi_mt = Event_Get_Semi_MT(evdev);
+  /* buttonpad means a physical button under the touch surface */
+  hwprops.is_button_pad = Event_Get_Button_Pad(evdev);
+  return hwprops;
+}
+
+// Callback from libgestures when a gesture is ready.
+void OnGestureReadyHelper(void* client_data, const Gesture* gesture) {
+  GestureInterpreterLibevdevCros* interpreter =
+      static_cast<GestureInterpreterLibevdevCros*>(client_data);
+  interpreter->OnGestureReady(gesture);
+}
+
+// Convert gestures timestamp (stime_t) to ui::Event timestamp.
+base::TimeDelta StimeToTimedelta(stime_t timestamp) {
+  return base::TimeDelta::FromMicroseconds(timestamp *
+                                           base::Time::kMicrosecondsPerSecond);
+}
+
+// Number of fingers for scroll gestures.
+const int kGestureScrollFingerCount = 2;
+
+// Number of fingers for swipe gestures.
+const int kGestureSwipeFingerCount = 3;
+
+}  // namespace
+
+GestureInterpreterLibevdevCros::GestureInterpreterLibevdevCros(
+    EventModifiersEvdev* modifiers,
+    CursorDelegateEvdev* cursor,
+    const EventDispatchCallback& callback)
+    : modifiers_(modifiers),
+      cursor_(cursor),
+      dispatch_callback_(callback),
+      interpreter_(NULL) {}
+
+GestureInterpreterLibevdevCros::~GestureInterpreterLibevdevCros() {
+  if (interpreter_) {
+    DeleteGestureInterpreter(interpreter_);
+    interpreter_ = NULL;
+  }
+}
+
+void GestureInterpreterLibevdevCros::OnLibEvdevCrosOpen(
+    Evdev* evdev,
+    EventStateRec* evstate) {
+  DCHECK(evdev->info.is_monotonic) << "libevdev must use monotonic timestamps";
+  VLOG(9) << "HACK DO NOT REMOVE OR LINK WILL FAIL" << (void*)gestures_log;
+
+  HardwareProperties hwprops = GestureHardwareProperties(evdev);
+  GestureInterpreterDeviceClass devclass = GestureDeviceClass(evdev);
+
+  // Create & initialize GestureInterpreter.
+  DCHECK(!interpreter_);
+  interpreter_ = NewGestureInterpreter();
+  GestureInterpreterInitialize(interpreter_, devclass);
+  GestureInterpreterSetHardwareProperties(interpreter_, &hwprops);
+  GestureInterpreterSetTimerProvider(
+      interpreter_,
+      const_cast<GesturesTimerProvider*>(&kGestureTimerProvider),
+      this);
+  GestureInterpreterSetCallback(interpreter_, OnGestureReadyHelper, this);
+}
+
+void GestureInterpreterLibevdevCros::OnLibEvdevCrosEvent(Evdev* evdev,
+                                                         EventStateRec* evstate,
+                                                         const timeval& time) {
+  HardwareState hwstate;
+  memset(&hwstate, 0, sizeof(hwstate));
+  hwstate.timestamp = StimeFromTimeval(&time);
+
+  // Mouse.
+  hwstate.rel_x = evstate->rel_x;
+  hwstate.rel_y = evstate->rel_y;
+  hwstate.rel_wheel = evstate->rel_wheel;
+  hwstate.rel_hwheel = evstate->rel_hwheel;
+
+  // Touch.
+  FingerState fingers[Event_Get_Slot_Count(evdev)];
+  memset(&fingers, 0, sizeof(fingers));
+  int current_finger = 0;
+  for (int i = 0; i < evstate->slot_count; i++) {
+    MtSlotPtr slot = &evstate->slots[i];
+    if (slot->tracking_id == -1)
+      continue;
+    fingers[current_finger].touch_major = slot->touch_major;
+    fingers[current_finger].touch_minor = slot->touch_minor;
+    fingers[current_finger].width_major = slot->width_major;
+    fingers[current_finger].width_minor = slot->width_minor;
+    fingers[current_finger].pressure = slot->pressure;
+    fingers[current_finger].orientation = slot->orientation;
+    fingers[current_finger].position_x = slot->position_x;
+    fingers[current_finger].position_y = slot->position_y;
+    fingers[current_finger].tracking_id = slot->tracking_id;
+    current_finger++;
+  }
+  hwstate.touch_cnt = Event_Get_Touch_Count(evdev);
+  hwstate.finger_cnt = current_finger;
+  hwstate.fingers = fingers;
+
+  // Buttons.
+  if (Event_Get_Button_Left(evdev))
+    hwstate.buttons_down |= GESTURES_BUTTON_LEFT;
+  if (Event_Get_Button_Middle(evdev))
+    hwstate.buttons_down |= GESTURES_BUTTON_MIDDLE;
+  if (Event_Get_Button_Right(evdev))
+    hwstate.buttons_down |= GESTURES_BUTTON_RIGHT;
+
+  GestureInterpreterPushHardwareState(interpreter_, &hwstate);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureReady(const Gesture* gesture) {
+  switch (gesture->type) {
+    case kGestureTypeMove:
+      OnGestureMove(gesture, &gesture->details.move);
+      break;
+    case kGestureTypeScroll:
+      OnGestureScroll(gesture, &gesture->details.scroll);
+      break;
+    case kGestureTypeButtonsChange:
+      OnGestureButtonsChange(gesture, &gesture->details.buttons);
+      break;
+    case kGestureTypeContactInitiated:
+      OnGestureContactInitiated(gesture);
+      break;
+    case kGestureTypeFling:
+      OnGestureFling(gesture, &gesture->details.fling);
+      break;
+    case kGestureTypeSwipe:
+      OnGestureSwipe(gesture, &gesture->details.swipe);
+      break;
+    case kGestureTypeSwipeLift:
+      OnGestureSwipeLift(gesture, &gesture->details.swipe_lift);
+      break;
+    case kGestureTypePinch:
+      OnGesturePinch(gesture, &gesture->details.pinch);
+      break;
+    case kGestureTypeMetrics:
+      OnGestureMetrics(gesture, &gesture->details.metrics);
+      break;
+    default:
+      LOG(WARNING) << base::StringPrintf("Unrecognized gesture type (%u)",
+                                         gesture->type);
+      break;
+  }
+}
+
+void GestureInterpreterLibevdevCros::OnGestureMove(const Gesture* gesture,
+                                                   const GestureMove* move) {
+  DVLOG(3) << base::StringPrintf("Gesture Move: (%f, %f) [%f, %f]",
+                                 move->dx,
+                                 move->dy,
+                                 move->ordinal_dx,
+                                 move->ordinal_dy);
+  if (!cursor_)
+    return;  // No cursor!
+
+  cursor_->MoveCursor(gfx::Vector2dF(move->dx, move->dy));
+  // TODO(spang): Use move->ordinal_dx, move->ordinal_dy
+  // TODO(spang): Use move->start_time, move->end_time
+  MouseEvent event(ET_MOUSE_MOVED,
+                   cursor_->location(),
+                   cursor_->location(),
+                   modifiers_->GetModifierFlags(),
+                   /* changed_button_flags */ 0);
+  Dispatch(&event);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureScroll(
+    const Gesture* gesture,
+    const GestureScroll* scroll) {
+  DVLOG(3) << base::StringPrintf("Gesture Scroll: (%f, %f) [%f, %f]",
+                                 scroll->dx,
+                                 scroll->dy,
+                                 scroll->ordinal_dx,
+                                 scroll->ordinal_dy);
+  if (!cursor_)
+    return;  // No cursor!
+
+  // TODO(spang): Support SetNaturalScroll
+  // TODO(spang): Use scroll->start_time
+  ScrollEvent event(ET_SCROLL,
+                    cursor_->location(),
+                    StimeToTimedelta(gesture->end_time),
+                    modifiers_->GetModifierFlags(),
+                    scroll->dx,
+                    scroll->dy,
+                    scroll->ordinal_dx,
+                    scroll->ordinal_dy,
+                    kGestureScrollFingerCount);
+  Dispatch(&event);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureButtonsChange(
+    const Gesture* gesture,
+    const GestureButtonsChange* buttons) {
+  DVLOG(3) << base::StringPrintf("Gesture Button Change: down=0x%02x up=0x%02x",
+                                 buttons->down,
+                                 buttons->up);
+
+  if (!cursor_)
+    return;  // No cursor!
+
+  // HACK for disabling TTC (actually, all clicks) on hidden cursor.
+  // This is normally plumbed via properties and can be removed soon.
+  // TODO(spang): Remove this.
+  if (buttons->down == GESTURES_BUTTON_LEFT &&
+      buttons->up == GESTURES_BUTTON_LEFT &&
+      !cursor_->IsCursorVisible())
+    return;
+
+  // TODO(spang): Use buttons->start_time, buttons->end_time
+  if (buttons->down & GESTURES_BUTTON_LEFT)
+    DispatchMouseButton(EVDEV_MODIFIER_LEFT_MOUSE_BUTTON, true);
+  if (buttons->down & GESTURES_BUTTON_MIDDLE)
+    DispatchMouseButton(EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON, true);
+  if (buttons->down & GESTURES_BUTTON_RIGHT)
+    DispatchMouseButton(EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON, true);
+  if (buttons->up & GESTURES_BUTTON_LEFT)
+    DispatchMouseButton(EVDEV_MODIFIER_LEFT_MOUSE_BUTTON, false);
+  if (buttons->up & GESTURES_BUTTON_MIDDLE)
+    DispatchMouseButton(EVDEV_MODIFIER_MIDDLE_MOUSE_BUTTON, false);
+  if (buttons->up & GESTURES_BUTTON_RIGHT)
+    DispatchMouseButton(EVDEV_MODIFIER_RIGHT_MOUSE_BUTTON, false);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureContactInitiated(
+    const Gesture* gesture) {
+  // TODO(spang): handle contact initiated.
+}
+
+void GestureInterpreterLibevdevCros::OnGestureFling(const Gesture* gesture,
+                                                    const GestureFling* fling) {
+  DVLOG(3) << base::StringPrintf(
+                  "Gesture Fling: (%f, %f) [%f, %f] fling_state=%d",
+                  fling->vx,
+                  fling->vy,
+                  fling->ordinal_vx,
+                  fling->ordinal_vy,
+                  fling->fling_state);
+
+  if (!cursor_)
+    return;  // No cursor!
+
+  EventType type =
+      (fling->fling_state == GESTURES_FLING_START ? ET_SCROLL_FLING_START
+                                                  : ET_SCROLL_FLING_CANCEL);
+
+  // Fling is like 2-finger scrolling but with velocity instead of displacement.
+  ScrollEvent event(type,
+                    cursor_->location(),
+                    StimeToTimedelta(gesture->end_time),
+                    modifiers_->GetModifierFlags(),
+                    fling->vx,
+                    fling->vy,
+                    fling->ordinal_vx,
+                    fling->ordinal_vy,
+                    kGestureScrollFingerCount);
+  Dispatch(&event);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureSwipe(const Gesture* gesture,
+                                                    const GestureSwipe* swipe) {
+  DVLOG(3) << base::StringPrintf("Gesture Swipe: (%f, %f) [%f, %f]",
+                                 swipe->dx,
+                                 swipe->dy,
+                                 swipe->ordinal_dx,
+                                 swipe->ordinal_dy);
+
+  if (!cursor_)
+    return;  // No cursor!
+
+  // Swipe is 3-finger scrolling.
+  ScrollEvent event(ET_SCROLL,
+                    cursor_->location(),
+                    StimeToTimedelta(gesture->end_time),
+                    modifiers_->GetModifierFlags(),
+                    swipe->dx,
+                    swipe->dy,
+                    swipe->ordinal_dx,
+                    swipe->ordinal_dy,
+                    kGestureSwipeFingerCount);
+  Dispatch(&event);
+}
+
+void GestureInterpreterLibevdevCros::OnGestureSwipeLift(
+    const Gesture* gesture,
+    const GestureSwipeLift* swipelift) {
+  DVLOG(3) << base::StringPrintf("Gesture Swipe Lift");
+
+  if (!cursor_)
+    return;  // No cursor!
+
+  // Turn a swipe lift into a fling start.
+  // TODO(spang): Figure out why and put it in this comment.
+
+  ScrollEvent event(ET_SCROLL_FLING_START,
+                    cursor_->location(),
+                    StimeToTimedelta(gesture->end_time),
+                    modifiers_->GetModifierFlags(),
+                    /* x_offset */ 0,
+                    /* y_offset */ 0,
+                    /* x_offset_ordinal */ 0,
+                    /* y_offset_ordinal */ 0,
+                    kGestureScrollFingerCount);
+  Dispatch(&event);
+
+}
+
+void GestureInterpreterLibevdevCros::OnGesturePinch(const Gesture* gesture,
+                                                    const GesturePinch* pinch) {
+  DVLOG(3) << base::StringPrintf(
+                  "Gesture Pinch: dz=%f [%f]", pinch->dz, pinch->ordinal_dz);
+
+  if (!cursor_)
+    return;  // No cursor!
+
+  NOTIMPLEMENTED();
+}
+
+void GestureInterpreterLibevdevCros::OnGestureMetrics(
+    const Gesture* gesture,
+    const GestureMetrics* metrics) {
+  DVLOG(3) << base::StringPrintf("Gesture Metrics: [%f, %f] type=%d",
+                                 metrics->data[0],
+                                 metrics->data[1],
+                                 metrics->type);
+  NOTIMPLEMENTED();
+}
+
+void GestureInterpreterLibevdevCros::Dispatch(Event* event) {
+  dispatch_callback_.Run(event);
+}
+
+void GestureInterpreterLibevdevCros::DispatchMouseButton(unsigned int modifier,
+                                                         bool down) {
+  const gfx::PointF& loc = cursor_->location();
+  int flag = modifiers_->GetEventFlagFromModifier(modifier);
+  EventType type = (down ? ET_MOUSE_PRESSED : ET_MOUSE_RELEASED);
+  modifiers_->UpdateModifier(modifier, down);
+  MouseEvent event(type, loc, loc, modifiers_->GetModifierFlags() | flag, flag);
+  Dispatch(&event);
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h
new file mode 100644
index 0000000..1044a11
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h
@@ -0,0 +1,90 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_INTERPRETER_LIBEVDEV_CROS_H_
+#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_INTERPRETER_LIBEVDEV_CROS_H_
+
+#include <gestures/gestures.h>
+#include <libevdev/libevdev.h>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/ozone/evdev/cursor_delegate_evdev.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+#include "ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h"
+
+namespace ui {
+
+class Event;
+class EventDeviceInfo;
+class EventModifiersEvdev;
+class CursorDelegateEvdev;
+
+typedef base::Callback<void(Event*)> EventDispatchCallback;
+
+// Convert libevdev-cros events to ui::Events using libgestures.
+//
+// This builds a GestureInterpreter for an input device (touchpad or
+// mouse).
+//
+// Raw input events must be preprocessed into a form suitable for
+// libgestures. The kernel protocol only emits changes to the device state,
+// so changes must be accumulated until a sync event. The full device state
+// at sync is then processed by libgestures.
+//
+// Once we have the state at sync, we convert it to a HardwareState object
+// and forward it to libgestures. If any gestures are produced, they are
+// converted to ui::Events and dispatched.
+class EVENTS_OZONE_EVDEV_EXPORT GestureInterpreterLibevdevCros
+    : public EventReaderLibevdevCros::Delegate {
+ public:
+  GestureInterpreterLibevdevCros(EventModifiersEvdev* modifiers,
+                                 CursorDelegateEvdev* cursor,
+                                 const EventDispatchCallback& callback);
+  virtual ~GestureInterpreterLibevdevCros();
+
+  // Overriden from ui::EventReaderLibevdevCros::Delegate
+  virtual void OnLibEvdevCrosOpen(Evdev* evdev,
+                                  EventStateRec* evstate) OVERRIDE;
+  virtual void OnLibEvdevCrosEvent(Evdev* evdev,
+                                   EventStateRec* evstate,
+                                   const timeval& time) OVERRIDE;
+
+  // Handler for gesture events generated from libgestures.
+  void OnGestureReady(const Gesture* gesture);
+
+ private:
+  void OnGestureMove(const Gesture* gesture, const GestureMove* move);
+  void OnGestureScroll(const Gesture* gesture, const GestureScroll* move);
+  void OnGestureButtonsChange(const Gesture* gesture,
+                              const GestureButtonsChange* move);
+  void OnGestureContactInitiated(const Gesture* gesture);
+  void OnGestureFling(const Gesture* gesture, const GestureFling* fling);
+  void OnGestureSwipe(const Gesture* gesture, const GestureSwipe* swipe);
+  void OnGestureSwipeLift(const Gesture* gesture,
+                          const GestureSwipeLift* swipelift);
+  void OnGesturePinch(const Gesture* gesture, const GesturePinch* pinch);
+  void OnGestureMetrics(const Gesture* gesture, const GestureMetrics* metrics);
+
+  void Dispatch(Event* event);
+  void DispatchMouseButton(unsigned int modifier, bool down);
+
+  // Shared modifier state.
+  EventModifiersEvdev* modifiers_;
+
+  // Shared cursor state.
+  CursorDelegateEvdev* cursor_;
+
+  // Callback for dispatching events.
+  EventDispatchCallback dispatch_callback_;
+
+  // Gestures interpretation state.
+  gestures::GestureInterpreter* interpreter_;
+
+  DISALLOW_COPY_AND_ASSIGN(GestureInterpreterLibevdevCros);
+};
+
+}  // namspace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_INTERPRETER_LIBEVDEV_CROS_H_
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc
new file mode 100644
index 0000000..009fc93
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc
@@ -0,0 +1,32 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/libgestures_glue/gesture_logging.h"
+
+#include <gestures/gestures.h>
+#include <stdarg.h>
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+
+namespace {
+
+std::string FormatLog(const char* fmt, va_list args) {
+  std::string msg = base::StringPrintV(fmt, args);
+  if (!msg.empty() && msg[msg.size() - 1] == '\n')
+    msg.erase(msg.end() - 1, msg.end());
+  return msg;
+}
+
+}  // namespace
+
+void gestures_log(int verb, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  if (verb <= GESTURES_LOG_ERROR)
+    LOG(ERROR) << "gestures: " << FormatLog(fmt, args);
+  else if (verb <= GESTURES_LOG_INFO)
+    VLOG(3) << "gestures: " << FormatLog(fmt, args);
+  va_end(args);
+}
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h b/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h
new file mode 100644
index 0000000..a5f5435
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h
@@ -0,0 +1,15 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_LOGGING_H_
+#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_LOGGING_H_
+
+// libgestures.so binds to this function for logging.
+// TODO(spang): Fix libgestures to not require this.
+extern "C"
+    __attribute__((visibility("default"))) void gestures_log(int verb,
+                                                             const char* fmt,
+                                                             ...);
+
+#endif  // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_LOGGING_H_
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc b/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc
new file mode 100644
index 0000000..6aad161
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc
@@ -0,0 +1,72 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h"
+
+#include <gestures/gestures.h>
+
+#include "base/timer/timer.h"
+
+// libgestures requires that this be in the top level namespace.
+class GesturesTimer {
+ public:
+  GesturesTimer() : callback_(NULL), callback_data_(NULL) {}
+  ~GesturesTimer() {}
+
+  void Set(stime_t delay, GesturesTimerCallback callback, void* callback_data) {
+    callback_ = callback;
+    callback_data_ = callback_data;
+    timer_.Start(FROM_HERE,
+                 base::TimeDelta::FromMicroseconds(
+                     delay * base::Time::kMicrosecondsPerSecond),
+                 this,
+                 &GesturesTimer::OnTimerExpired);
+  }
+
+  void Cancel() { timer_.Stop(); }
+
+ private:
+  void OnTimerExpired() {
+    struct timespec ts;
+    DCHECK(!clock_gettime(CLOCK_MONOTONIC, &ts));
+    stime_t next_delay = callback_(StimeFromTimespec(&ts), callback_data_);
+    if (next_delay >= 0) {
+      timer_.Start(FROM_HERE,
+                   base::TimeDelta::FromMicroseconds(
+                       next_delay * base::Time::kMicrosecondsPerSecond),
+                   this,
+                   &GesturesTimer::OnTimerExpired);
+    }
+  }
+
+  GesturesTimerCallback callback_;
+  void* callback_data_;
+  base::OneShotTimer<GesturesTimer> timer_;
+};
+
+namespace ui {
+
+namespace {
+
+GesturesTimer* GesturesTimerCreate(void* data) { return new GesturesTimer; }
+
+void GesturesTimerSet(void* data,
+                      GesturesTimer* timer,
+                      stime_t delay,
+                      GesturesTimerCallback callback,
+                      void* callback_data) {
+  timer->Set(delay, callback, callback_data);
+}
+
+void GesturesTimerCancel(void* data, GesturesTimer* timer) { timer->Cancel(); }
+
+void GesturesTimerFree(void* data, GesturesTimer* timer) { delete timer; }
+
+}  // namespace
+
+const GesturesTimerProvider kGestureTimerProvider = {
+    GesturesTimerCreate, GesturesTimerSet, GesturesTimerCancel,
+    GesturesTimerFree};
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h b/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h
new file mode 100644
index 0000000..edc20ba
--- /dev/null
+++ b/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h
@@ -0,0 +1,16 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_TIMER_PROVIDER_H_
+#define UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_TIMER_PROVIDER_H_
+
+#include <gestures/gestures.h>
+
+namespace ui {
+
+extern const GesturesTimerProvider kGestureTimerProvider;
+
+}  // namspace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_GESTURE_TIMER_PROVIDER_H_
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
new file mode 100644
index 0000000..7b666e0
--- /dev/null
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
@@ -0,0 +1,302 @@
+// Copyright 2014 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 "ui/events/ozone/evdev/touch_event_converter_evdev.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <poll.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <cmath>
+#include <limits>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_switches.h"
+#include "ui/gfx/screen.h"
+
+namespace {
+
+struct TouchCalibration {
+  int bezel_left;
+  int bezel_right;
+  int bezel_top;
+  int bezel_bottom;
+};
+
+void GetTouchCalibration(TouchCalibration* cal) {
+  std::vector<std::string> parts;
+  if (Tokenize(CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+                   switches::kTouchCalibration),
+               ",",
+               &parts) >= 4) {
+    if (!base::StringToInt(parts[0], &cal->bezel_left))
+      DLOG(ERROR) << "Incorrect left border calibration value passed.";
+    if (!base::StringToInt(parts[1], &cal->bezel_right))
+      DLOG(ERROR) << "Incorrect right border calibration value passed.";
+    if (!base::StringToInt(parts[2], &cal->bezel_top))
+      DLOG(ERROR) << "Incorrect top border calibration value passed.";
+    if (!base::StringToInt(parts[3], &cal->bezel_bottom))
+      DLOG(ERROR) << "Incorrect bottom border calibration value passed.";
+  }
+}
+
+float TuxelsToPixels(float val,
+                     float min_tuxels,
+                     float num_tuxels,
+                     float min_pixels,
+                     float num_pixels) {
+  // Map [min_tuxels, min_tuxels + num_tuxels) to
+  //     [min_pixels, min_pixels + num_pixels).
+  return min_pixels + (val - min_tuxels) * num_pixels / num_tuxels;
+}
+
+float TuxelToPixelSize(float val, float num_tuxels, float num_pixels) {
+  return val * num_pixels / num_tuxels;
+}
+
+}  // namespace
+
+namespace ui {
+
+TouchEventConverterEvdev::TouchEventConverterEvdev(
+    int fd,
+    base::FilePath path,
+    int id,
+    const EventDeviceInfo& info,
+    const EventDispatchCallback& callback)
+    : EventConverterEvdev(fd, path, id),
+      callback_(callback),
+      syn_dropped_(false),
+      is_type_a_(false),
+      current_slot_(0) {
+  Init(info);
+}
+
+TouchEventConverterEvdev::~TouchEventConverterEvdev() {
+  Stop();
+  close(fd_);
+}
+
+void TouchEventConverterEvdev::Init(const EventDeviceInfo& info) {
+  gfx::Screen* screen = gfx::Screen::GetScreenByType(gfx::SCREEN_TYPE_NATIVE);
+  if (!screen)
+    return;  // No scaling.
+  gfx::Display display = screen->GetPrimaryDisplay();
+  gfx::Size size = display.GetSizeInPixel();
+
+  pressure_min_ = info.GetAbsMinimum(ABS_MT_PRESSURE);
+  pressure_max_ = info.GetAbsMaximum(ABS_MT_PRESSURE);
+  x_min_tuxels_ = info.GetAbsMinimum(ABS_MT_POSITION_X);
+  x_num_tuxels_ = info.GetAbsMaximum(ABS_MT_POSITION_X) - x_min_tuxels_ + 1;
+  y_min_tuxels_ = info.GetAbsMinimum(ABS_MT_POSITION_Y);
+  y_num_tuxels_ = info.GetAbsMaximum(ABS_MT_POSITION_Y) - y_min_tuxels_ + 1;
+  native_size_ = gfx::Size(x_num_tuxels_, y_num_tuxels_);
+
+  // Map coordinates onto screen.
+  x_min_pixels_ = 0;
+  y_min_pixels_ = 0;
+  x_num_pixels_ = size.width();
+  y_num_pixels_ = size.height();
+
+  VLOG(1) << "mapping touch coordinates to screen coordinates: "
+          << base::StringPrintf("%dx%d", size.width(), size.height());
+
+  // Apply --touch-calibration.
+  TouchCalibration cal = {};
+  GetTouchCalibration(&cal);
+  x_min_tuxels_ += cal.bezel_left;
+  x_num_tuxels_ -= cal.bezel_left + cal.bezel_right;
+  y_min_tuxels_ += cal.bezel_top;
+  y_num_tuxels_ -= cal.bezel_top + cal.bezel_bottom;
+
+  VLOG(1) << "applying touch calibration: "
+          << base::StringPrintf("[%d, %d, %d, %d]",
+                                cal.bezel_left,
+                                cal.bezel_right,
+                                cal.bezel_top,
+                                cal.bezel_bottom);
+}
+
+bool TouchEventConverterEvdev::Reinitialize() {
+  EventDeviceInfo info;
+  if (info.Initialize(fd_)) {
+    Init(info);
+    return true;
+  }
+  return false;
+}
+
+bool TouchEventConverterEvdev::HasTouchscreen() const {
+  return true;
+}
+
+gfx::Size TouchEventConverterEvdev::GetTouchscreenSize() const {
+  return native_size_;
+}
+
+void TouchEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) {
+  input_event inputs[MAX_FINGERS * 6 + 1];
+  ssize_t read_size = read(fd, inputs, sizeof(inputs));
+  if (read_size < 0) {
+    if (errno == EINTR || errno == EAGAIN)
+      return;
+    if (errno != ENODEV)
+      PLOG(ERROR) << "error reading device " << path_.value();
+    Stop();
+    return;
+  }
+
+  for (unsigned i = 0; i < read_size / sizeof(*inputs); i++) {
+    ProcessInputEvent(inputs[i]);
+  }
+}
+
+void TouchEventConverterEvdev::ProcessInputEvent(const input_event& input) {
+  if (input.type == EV_SYN) {
+    ProcessSyn(input);
+  } else if(syn_dropped_) {
+    // Do nothing. This branch indicates we have lost sync with the driver.
+  } else if (input.type == EV_ABS) {
+    if (current_slot_ >= MAX_FINGERS) {
+      LOG(ERROR) << "too many touch events: " << current_slot_;
+      return;
+    }
+    ProcessAbs(input);
+  } else if (input.type == EV_KEY) {
+    switch (input.code) {
+      case BTN_TOUCH:
+        break;
+      default:
+        NOTIMPLEMENTED() << "invalid code for EV_KEY: " << input.code;
+    }
+  } else {
+    NOTIMPLEMENTED() << "invalid type: " << input.type;
+  }
+}
+
+void TouchEventConverterEvdev::ProcessAbs(const input_event& input) {
+  switch (input.code) {
+    case ABS_MT_TOUCH_MAJOR:
+      altered_slots_.set(current_slot_);
+      // TODO(spang): If we have all of major, minor, and orientation,
+      // we can scale the ellipse correctly. However on the Pixel we get
+      // neither minor nor orientation, so this is all we can do.
+      events_[current_slot_].radius_x_ =
+          TuxelToPixelSize(input.value, x_num_tuxels_, x_num_pixels_) / 2.0f;
+      break;
+    case ABS_MT_TOUCH_MINOR:
+      altered_slots_.set(current_slot_);
+      events_[current_slot_].radius_y_ =
+          TuxelToPixelSize(input.value, y_num_tuxels_, y_num_pixels_) / 2.0f;
+      break;
+    case ABS_MT_POSITION_X:
+      altered_slots_.set(current_slot_);
+      events_[current_slot_].x_ = TuxelsToPixels(input.value,
+                                                 x_min_tuxels_,
+                                                 x_num_tuxels_,
+                                                 x_min_pixels_,
+                                                 x_num_pixels_);
+      break;
+    case ABS_MT_POSITION_Y:
+      altered_slots_.set(current_slot_);
+      events_[current_slot_].y_ = TuxelsToPixels(input.value,
+                                                 y_min_tuxels_,
+                                                 y_num_tuxels_,
+                                                 y_min_pixels_,
+                                                 y_num_pixels_);
+      break;
+    case ABS_MT_TRACKING_ID:
+      altered_slots_.set(current_slot_);
+      if (input.value < 0) {
+        events_[current_slot_].type_ = ET_TOUCH_RELEASED;
+      } else {
+        events_[current_slot_].finger_ = input.value;
+        events_[current_slot_].type_ = ET_TOUCH_PRESSED;
+      }
+      break;
+    case ABS_MT_PRESSURE:
+      altered_slots_.set(current_slot_);
+      events_[current_slot_].pressure_ = input.value - pressure_min_;
+      events_[current_slot_].pressure_ /= pressure_max_ - pressure_min_;
+      break;
+    case ABS_MT_SLOT:
+      current_slot_ = input.value;
+      altered_slots_.set(current_slot_);
+      break;
+    default:
+      DVLOG(5) << "unhandled code for EV_ABS: " << input.code;
+  }
+}
+
+void TouchEventConverterEvdev::ProcessSyn(const input_event& input) {
+  switch (input.code) {
+    case SYN_REPORT:
+      if (syn_dropped_) {
+        // Have to re-initialize.
+        if (Reinitialize()) {
+          syn_dropped_ = false;
+          altered_slots_.reset();
+        } else {
+          LOG(ERROR) << "failed to re-initialize device info";
+        }
+      } else {
+        ReportEvents(base::TimeDelta::FromMicroseconds(
+            input.time.tv_sec * 1000000 + input.time.tv_usec));
+      }
+      if (is_type_a_)
+        current_slot_ = 0;
+      break;
+    case SYN_MT_REPORT:
+      // For type A devices, we just get a stream of all current contacts,
+      // in some arbitrary order.
+      events_[current_slot_++].type_ = ET_TOUCH_PRESSED;
+      is_type_a_ = true;
+      break;
+    case SYN_DROPPED:
+      // Some buffer has overrun. We ignore all events up to and
+      // including the next SYN_REPORT.
+      syn_dropped_ = true;
+      break;
+    default:
+      NOTIMPLEMENTED() << "invalid code for EV_SYN: " << input.code;
+  }
+}
+
+void TouchEventConverterEvdev::ReportEvents(base::TimeDelta delta) {
+  for (int i = 0; i < MAX_FINGERS; i++) {
+    if (altered_slots_[i]) {
+      // TODO(rikroege): Support elliptical finger regions.
+      TouchEvent evt(events_[i].type_,
+                     gfx::PointF(events_[i].x_, events_[i].y_),
+                     /* flags */ 0,
+                     /* touch_id */ i,
+                     delta,
+                     /* radius_x */ events_[i].radius_x_,
+                     /* radius_y */ events_[i].radius_y_,
+                     /* angle */ 0.,
+                     events_[i].pressure_);
+      callback_.Run(&evt);
+
+      // Subsequent events for this finger will be touch-move until it
+      // is released.
+      events_[i].type_ = ET_TOUCH_MOVED;
+    }
+  }
+  altered_slots_.reset();
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.h b/ui/events/ozone/evdev/touch_event_converter_evdev.h
new file mode 100644
index 0000000..f85e0fe
--- /dev/null
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.h
@@ -0,0 +1,115 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_
+
+#include <bitset>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/message_loop/message_pump_libevent.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/ozone/evdev/event_converter_evdev.h"
+#include "ui/events/ozone/evdev/event_device_info.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+
+namespace ui {
+
+class TouchEvent;
+
+class EVENTS_OZONE_EVDEV_EXPORT TouchEventConverterEvdev
+    : public EventConverterEvdev {
+ public:
+  enum {
+    MAX_FINGERS = 11
+  };
+  TouchEventConverterEvdev(int fd,
+                           base::FilePath path,
+                           int id,
+                           const EventDeviceInfo& info,
+                           const EventDispatchCallback& dispatch);
+  virtual ~TouchEventConverterEvdev();
+
+  // EventConverterEvdev:
+  virtual bool HasTouchscreen() const OVERRIDE;
+  virtual gfx::Size GetTouchscreenSize() const OVERRIDE;
+
+ private:
+  friend class MockTouchEventConverterEvdev;
+
+  // Unsafe part of initialization.
+  void Init(const EventDeviceInfo& info);
+
+  // Overidden from base::MessagePumpLibevent::Watcher.
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+
+  virtual bool Reinitialize();
+
+  void ProcessInputEvent(const input_event& input);
+  void ProcessAbs(const input_event& input);
+  void ProcessSyn(const input_event& input);
+
+  void ReportEvents(base::TimeDelta delta);
+
+  // Callback for dispatching events.
+  EventDispatchCallback callback_;
+
+  // Set if we have seen a SYN_DROPPED and not yet re-synced with the device.
+  bool syn_dropped_;
+
+  // Set if this is a type A device (uses SYN_MT_REPORT).
+  bool is_type_a_;
+
+  // Pressure values.
+  int pressure_min_;
+  int pressure_max_;  // Used to normalize pressure values.
+
+  // Input range for x-axis.
+  float x_min_tuxels_;
+  float x_num_tuxels_;
+
+  // Input range for y-axis.
+  float y_min_tuxels_;
+  float y_num_tuxels_;
+
+  // Output range for x-axis.
+  float x_min_pixels_;
+  float x_num_pixels_;
+
+  // Output range for y-axis.
+  float y_min_pixels_;
+  float y_num_pixels_;
+
+  // Size of the touchscreen as reported by the driver.
+  gfx::Size native_size_;
+
+  // Touch point currently being updated from the /dev/input/event* stream.
+  int current_slot_;
+
+  // Bit field tracking which in-progress touch points have been modified
+  // without a syn event.
+  std::bitset<MAX_FINGERS> altered_slots_;
+
+  struct InProgressEvents {
+    float x_;
+    float y_;
+    int id_;  // Device reported "unique" touch point id; -1 means not active
+    int finger_;  // "Finger" id starting from 0; -1 means not active
+
+    EventType type_;
+    float radius_x_;
+    float radius_y_;
+    float pressure_;
+  };
+
+  // In-progress touch points.
+  InProgressEvents events_[MAX_FINGERS];
+
+  DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdev);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
new file mode 100644
index 0000000..95dc5c4
--- /dev/null
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -0,0 +1,529 @@
+// Copyright 2014 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 <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/posix/eintr_wrapper.h"
+#include "base/run_loop.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/ozone/evdev/touch_event_converter_evdev.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_source.h"
+
+namespace {
+
+static int SetNonBlocking(int fd) {
+  int flags = fcntl(fd, F_GETFL, 0);
+  if (flags == -1)
+    flags = 0;
+  return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+}
+
+const char kTestDevicePath[] = "/dev/input/test-device";
+
+}  // namespace
+
+namespace ui {
+
+class MockTouchEventConverterEvdev : public TouchEventConverterEvdev {
+ public:
+  MockTouchEventConverterEvdev(int fd, base::FilePath path);
+  virtual ~MockTouchEventConverterEvdev() {};
+
+  void ConfigureReadMock(struct input_event* queue,
+                         long read_this_many,
+                         long queue_index);
+
+  unsigned size() { return dispatched_events_.size(); }
+  TouchEvent* event(unsigned index) { return dispatched_events_[index]; }
+
+  // Actually dispatch the event reader code.
+  void ReadNow() {
+    OnFileCanReadWithoutBlocking(read_pipe_);
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void DispatchCallback(Event* event) {
+    dispatched_events_.push_back(
+        new TouchEvent(*static_cast<TouchEvent*>(event)));
+  }
+
+  virtual bool Reinitialize() OVERRIDE { return true; }
+
+ private:
+  int read_pipe_;
+  int write_pipe_;
+
+  ScopedVector<TouchEvent> dispatched_events_;
+
+  DISALLOW_COPY_AND_ASSIGN(MockTouchEventConverterEvdev);
+};
+
+MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int fd,
+                                                           base::FilePath path)
+    : TouchEventConverterEvdev(
+          fd,
+          path,
+          1,
+          EventDeviceInfo(),
+          base::Bind(&MockTouchEventConverterEvdev::DispatchCallback,
+                     base::Unretained(this))) {
+  pressure_min_ = 30;
+  pressure_max_ = 60;
+
+  // TODO(rjkroege): Check test axes.
+  x_min_pixels_ = x_min_tuxels_ = 0;
+  x_num_pixels_ = x_num_tuxels_ = std::numeric_limits<int>::max();
+  y_min_pixels_ = y_min_tuxels_ = 0;
+  y_num_pixels_ = y_num_tuxels_ = std::numeric_limits<int>::max();
+
+  int fds[2];
+
+  if (pipe(fds))
+    PLOG(FATAL) << "failed pipe";
+
+  DCHECK(SetNonBlocking(fds[0]) == 0)
+      << "SetNonBlocking for pipe fd[0] failed, errno: " << errno;
+  DCHECK(SetNonBlocking(fds[1]) == 0)
+      << "SetNonBlocking for pipe fd[0] failed, errno: " << errno;
+  read_pipe_ = fds[0];
+  write_pipe_ = fds[1];
+}
+
+void MockTouchEventConverterEvdev::ConfigureReadMock(struct input_event* queue,
+                                                     long read_this_many,
+                                                     long queue_index) {
+  int nwrite = HANDLE_EINTR(write(write_pipe_,
+                                  queue + queue_index,
+                                  sizeof(struct input_event) * read_this_many));
+  DCHECK(nwrite ==
+         static_cast<int>(sizeof(struct input_event) * read_this_many))
+      << "write() failed, errno: " << errno;
+}
+
+}  // namespace ui
+
+// Test fixture.
+class TouchEventConverterEvdevTest : public testing::Test {
+ public:
+  TouchEventConverterEvdevTest() {}
+
+  // Overridden from testing::Test:
+  virtual void SetUp() OVERRIDE {
+    // Set up pipe to satisfy message pump (unused).
+    int evdev_io[2];
+    if (pipe(evdev_io))
+      PLOG(FATAL) << "failed pipe";
+    events_in_ = evdev_io[0];
+    events_out_ = evdev_io[1];
+
+    loop_ = new base::MessageLoopForUI;
+    device_ = new ui::MockTouchEventConverterEvdev(
+        events_in_, base::FilePath(kTestDevicePath));
+  }
+
+  virtual void TearDown() OVERRIDE {
+    delete device_;
+    delete loop_;
+  }
+
+  ui::MockTouchEventConverterEvdev* device() { return device_; }
+
+ private:
+  base::MessageLoop* loop_;
+  ui::MockTouchEventConverterEvdev* device_;
+
+  int events_out_;
+  int events_in_;
+
+  DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdevTest);
+};
+
+// TODO(rjkroege): Test for valid handling of time stamps.
+TEST_F(TouchEventConverterEvdevTest, TouchDown) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 684},
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  dev->ConfigureReadMock(mock_kernel_queue, 1, 0);
+  dev->ReadNow();
+  EXPECT_EQ(0u, dev->size());
+
+  dev->ConfigureReadMock(mock_kernel_queue, 2, 1);
+  dev->ReadNow();
+  EXPECT_EQ(0u, dev->size());
+
+  dev->ConfigureReadMock(mock_kernel_queue, 3, 3);
+  dev->ReadNow();
+  EXPECT_EQ(1u, dev->size());
+
+  ui::TouchEvent* event = dev->event(0);
+  EXPECT_FALSE(event == NULL);
+
+  EXPECT_EQ(ui::ET_TOUCH_PRESSED, event->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+  EXPECT_EQ(42, event->x());
+  EXPECT_EQ(51, event->y());
+  EXPECT_EQ(0, event->touch_id());
+  EXPECT_FLOAT_EQ(1.5f, event->radius_x());
+  EXPECT_FLOAT_EQ(.5f, event->force());
+  EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, NoEvents) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+  dev->ConfigureReadMock(NULL, 0, 0);
+  EXPECT_EQ(0u, dev->size());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TouchMove) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue_press[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 684},
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  struct input_event mock_kernel_queue_move1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 50},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 43}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  struct input_event mock_kernel_queue_move2[] = {
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 42}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  // Setup and discard a press.
+  dev->ConfigureReadMock(mock_kernel_queue_press, 6, 0);
+  dev->ReadNow();
+  EXPECT_EQ(1u, dev->size());
+
+  dev->ConfigureReadMock(mock_kernel_queue_move1, 4, 0);
+  dev->ReadNow();
+  EXPECT_EQ(2u, dev->size());
+  ui::TouchEvent* event = dev->event(1);
+  EXPECT_FALSE(event == NULL);
+
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, event->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+  EXPECT_EQ(42, event->x());
+  EXPECT_EQ(43, event->y());
+  EXPECT_EQ(0, event->touch_id());
+  EXPECT_FLOAT_EQ(2.f / 3.f, event->force());
+  EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+
+  dev->ConfigureReadMock(mock_kernel_queue_move2, 2, 0);
+  dev->ReadNow();
+  EXPECT_EQ(3u, dev->size());
+  event = dev->event(2);
+  EXPECT_FALSE(event == NULL);
+
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, event->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+  EXPECT_EQ(42, event->x());
+  EXPECT_EQ(42, event->y());
+  EXPECT_EQ(0, event->touch_id());
+  EXPECT_FLOAT_EQ(2.f / 3.f, event->force());
+  EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TouchRelease) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue_press[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 684},
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  struct input_event mock_kernel_queue_release[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, -1}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  // Setup and discard a press.
+  dev->ConfigureReadMock(mock_kernel_queue_press, 6, 0);
+  dev->ReadNow();
+  EXPECT_EQ(1u, dev->size());
+  ui::TouchEvent* event = dev->event(0);
+  EXPECT_FALSE(event == NULL);
+
+  dev->ConfigureReadMock(mock_kernel_queue_release, 2, 0);
+  dev->ReadNow();
+  EXPECT_EQ(2u, dev->size());
+  event = dev->event(1);
+  EXPECT_FALSE(event == NULL);
+
+  EXPECT_EQ(ui::ET_TOUCH_RELEASED, event->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), event->time_stamp());
+  EXPECT_EQ(42, event->x());
+  EXPECT_EQ(51, event->y());
+  EXPECT_EQ(0, event->touch_id());
+  EXPECT_FLOAT_EQ(.5f, event->force());
+  EXPECT_FLOAT_EQ(0.f, event->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TwoFingerGesture) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  ui::TouchEvent* ev0;
+  ui::TouchEvent* ev1;
+
+  struct input_event mock_kernel_queue_press0[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 684},
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+  // Setup and discard a press.
+  dev->ConfigureReadMock(mock_kernel_queue_press0, 6, 0);
+  dev->ReadNow();
+  EXPECT_EQ(1u, dev->size());
+
+  struct input_event mock_kernel_queue_move0[] = {
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+  // Setup and discard a move.
+  dev->ConfigureReadMock(mock_kernel_queue_move0, 2, 0);
+  dev->ReadNow();
+  EXPECT_EQ(2u, dev->size());
+
+  struct input_event mock_kernel_queue_move0press1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0},
+    {{0, 0}, EV_ABS, ABS_MT_SLOT, 1}, {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 686},
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 101},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 102}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+  // Move on 0, press on 1.
+  dev->ConfigureReadMock(mock_kernel_queue_move0press1, 9, 0);
+  dev->ReadNow();
+  EXPECT_EQ(4u, dev->size());
+  ev0 = dev->event(2);
+  ev1 = dev->event(3);
+
+  // Move
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, ev0->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev0->time_stamp());
+  EXPECT_EQ(40, ev0->x());
+  EXPECT_EQ(51, ev0->y());
+  EXPECT_EQ(0, ev0->touch_id());
+  EXPECT_FLOAT_EQ(.5f, ev0->force());
+  EXPECT_FLOAT_EQ(0.f, ev0->rotation_angle());
+
+  // Press
+  EXPECT_EQ(ui::ET_TOUCH_PRESSED, ev1->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+  EXPECT_EQ(101, ev1->x());
+  EXPECT_EQ(102, ev1->y());
+  EXPECT_EQ(1, ev1->touch_id());
+  EXPECT_FLOAT_EQ(.5f, ev1->force());
+  EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+
+  // Stationary 0, Moves 1.
+  struct input_event mock_kernel_queue_stationary0_move1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+  dev->ConfigureReadMock(mock_kernel_queue_stationary0_move1, 2, 0);
+  dev->ReadNow();
+  EXPECT_EQ(5u, dev->size());
+  ev1 = dev->event(4);
+
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, ev1->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+  EXPECT_EQ(40, ev1->x());
+  EXPECT_EQ(102, ev1->y());
+  EXPECT_EQ(1, ev1->touch_id());
+
+  EXPECT_FLOAT_EQ(.5f, ev1->force());
+  EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+
+  // Move 0, stationary 1.
+  struct input_event mock_kernel_queue_move0_stationary1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_SLOT, 0}, {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 39},
+    {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+  dev->ConfigureReadMock(mock_kernel_queue_move0_stationary1, 3, 0);
+  dev->ReadNow();
+  EXPECT_EQ(6u, dev->size());
+  ev0 = dev->event(5);
+
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, ev0->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev0->time_stamp());
+  EXPECT_EQ(39, ev0->x());
+  EXPECT_EQ(51, ev0->y());
+  EXPECT_EQ(0, ev0->touch_id());
+  EXPECT_FLOAT_EQ(.5f, ev0->force());
+  EXPECT_FLOAT_EQ(0.f, ev0->rotation_angle());
+
+  // Release 0, move 1.
+  struct input_event mock_kernel_queue_release0_move1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, -1}, {{0, 0}, EV_ABS, ABS_MT_SLOT, 1},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 38}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+  dev->ConfigureReadMock(mock_kernel_queue_release0_move1, 4, 0);
+  dev->ReadNow();
+  EXPECT_EQ(8u, dev->size());
+  ev0 = dev->event(6);
+  ev1 = dev->event(7);
+
+  EXPECT_EQ(ui::ET_TOUCH_RELEASED, ev0->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev0->time_stamp());
+  EXPECT_EQ(39, ev0->x());
+  EXPECT_EQ(51, ev0->y());
+  EXPECT_EQ(0, ev0->touch_id());
+  EXPECT_FLOAT_EQ(.5f, ev0->force());
+  EXPECT_FLOAT_EQ(0.f, ev0->rotation_angle());
+
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, ev1->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+  EXPECT_EQ(38, ev1->x());
+  EXPECT_EQ(102, ev1->y());
+  EXPECT_EQ(1, ev1->touch_id());
+  EXPECT_FLOAT_EQ(.5f, ev1->force());
+  EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+
+  // Release 1.
+  struct input_event mock_kernel_queue_release1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, -1}, {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+  dev->ConfigureReadMock(mock_kernel_queue_release1, 2, 0);
+  dev->ReadNow();
+  EXPECT_EQ(9u, dev->size());
+  ev1 = dev->event(8);
+
+  EXPECT_EQ(ui::ET_TOUCH_RELEASED, ev1->type());
+  EXPECT_EQ(base::TimeDelta::FromMicroseconds(0), ev1->time_stamp());
+  EXPECT_EQ(38, ev1->x());
+  EXPECT_EQ(102, ev1->y());
+  EXPECT_EQ(1, ev1->touch_id());
+  EXPECT_FLOAT_EQ(.5f, ev1->force());
+  EXPECT_FLOAT_EQ(0.f, ev1->rotation_angle());
+}
+
+TEST_F(TouchEventConverterEvdevTest, TypeA) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue_press0[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51},
+    {{0, 0}, EV_SYN, SYN_MT_REPORT, 0},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 61},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 71},
+    {{0, 0}, EV_SYN, SYN_MT_REPORT, 0},
+    {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  // Check that two events are generated.
+  dev->ConfigureReadMock(mock_kernel_queue_press0, 10, 0);
+  dev->ReadNow();
+  EXPECT_EQ(2u, dev->size());
+}
+
+TEST_F(TouchEventConverterEvdevTest, Unsync) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue_press0[] = {
+    {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 684},
+    {{0, 0}, EV_ABS, ABS_MT_TOUCH_MAJOR, 3},
+    {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 45},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 42},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 51}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  dev->ConfigureReadMock(mock_kernel_queue_press0, 6, 0);
+  dev->ReadNow();
+  EXPECT_EQ(1u, dev->size());
+
+  // Prepare a move with a drop.
+  struct input_event mock_kernel_queue_move0[] = {
+    {{0, 0}, EV_SYN, SYN_DROPPED, 0},
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  // Verify that we didn't receive it/
+  dev->ConfigureReadMock(mock_kernel_queue_move0, 3, 0);
+  dev->ReadNow();
+  EXPECT_EQ(1u, dev->size());
+
+  struct input_event mock_kernel_queue_move1[] = {
+    {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 40}, {{0, 0}, EV_SYN, SYN_REPORT, 0}
+  };
+
+  // Verify that it re-syncs after a SYN_REPORT.
+  dev->ConfigureReadMock(mock_kernel_queue_move1, 2, 0);
+  dev->ReadNow();
+  EXPECT_EQ(2u, dev->size());
+}
+
+// crbug.com/407386
+TEST_F(TouchEventConverterEvdevTest,
+       DontChangeMultitouchPositionFromLegacyAxes) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+
+  struct input_event mock_kernel_queue[] = {
+      {{0, 0}, EV_ABS, ABS_MT_SLOT, 0},
+      {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 100},
+      {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 999},
+      {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 888},
+      {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 55},
+      {{0, 0}, EV_ABS, ABS_MT_SLOT, 1},
+      {{0, 0}, EV_ABS, ABS_MT_TRACKING_ID, 200},
+      {{0, 0}, EV_ABS, ABS_MT_PRESSURE, 44},
+      {{0, 0}, EV_ABS, ABS_MT_POSITION_X, 777},
+      {{0, 0}, EV_ABS, ABS_MT_POSITION_Y, 666},
+      {{0, 0}, EV_ABS, ABS_X, 999},
+      {{0, 0}, EV_ABS, ABS_Y, 888},
+      {{0, 0}, EV_ABS, ABS_PRESSURE, 55},
+      {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  // Check that two events are generated.
+  dev->ConfigureReadMock(mock_kernel_queue, arraysize(mock_kernel_queue), 0);
+  dev->ReadNow();
+
+  const unsigned int kExpectedEventCount = 2;
+  EXPECT_EQ(kExpectedEventCount, dev->size());
+  if (kExpectedEventCount != dev->size())
+    return;
+
+  ui::TouchEvent* ev0 = dev->event(0);
+  ui::TouchEvent* ev1 = dev->event(1);
+
+  EXPECT_EQ(0, ev0->touch_id());
+  EXPECT_EQ(999, ev0->x());
+  EXPECT_EQ(888, ev0->y());
+  EXPECT_FLOAT_EQ(0.8333333f, ev0->force());
+
+  EXPECT_EQ(1, ev1->touch_id());
+  EXPECT_EQ(777, ev1->x());
+  EXPECT_EQ(666, ev1->y());
+  EXPECT_FLOAT_EQ(0.4666666f, ev1->force());
+}
diff --git a/ui/events/ozone/events_ozone.cc b/ui/events/ozone/events_ozone.cc
new file mode 100644
index 0000000..d2e1e33
--- /dev/null
+++ b/ui/events/ozone/events_ozone.cc
@@ -0,0 +1,201 @@
+// Copyright (c) 2013 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 "ui/events/ozone/events_ozone.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+
+namespace ui {
+
+void UpdateDeviceList() { NOTIMPLEMENTED(); }
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+  const ui::Event* event = static_cast<const ui::Event*>(native_event);
+  return event->time_stamp();
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+  const ui::Event* event = static_cast<const ui::Event*>(native_event);
+  return event->flags();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+  const ui::Event* event = static_cast<const ui::Event*>(native_event);
+  return event->type();
+}
+
+gfx::Point EventSystemLocationFromNative(
+    const base::NativeEvent& native_event) {
+  const ui::LocatedEvent* e =
+      static_cast<const ui::LocatedEvent*>(native_event);
+  DCHECK(e->IsMouseEvent() || e->IsTouchEvent() || e->IsGestureEvent() ||
+         e->IsScrollEvent());
+  return e->location();
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+  return EventSystemLocationFromNative(native_event);
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+    const base::NativeEvent& native_event) {
+  const ui::MouseEvent* event =
+      static_cast<const ui::MouseEvent*>(native_event);
+  DCHECK(event->IsMouseEvent() || event->IsScrollEvent());
+  return event->changed_button_flags();
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+  const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+  DCHECK(event->IsKeyEvent());
+  return event->key_code();
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+  const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+  DCHECK(event->IsKeyEvent());
+  return event->code().c_str();
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+  const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+  DCHECK(event->IsKeyEvent());
+  return event->platform_keycode();
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+  const ui::KeyEvent* event = static_cast<const ui::KeyEvent*>(native_event);
+  DCHECK(event->IsKeyEvent());
+  return event->is_char();
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+  const ui::MouseWheelEvent* event =
+      static_cast<const ui::MouseWheelEvent*>(native_event);
+  DCHECK(event->type() == ET_MOUSEWHEEL);
+  return event->offset();
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+  return NULL;
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& event) {
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
+}
+
+int GetTouchId(const base::NativeEvent& native_event) {
+  const ui::TouchEvent* event =
+      static_cast<const ui::TouchEvent*>(native_event);
+  DCHECK(event->IsTouchEvent());
+  return event->touch_id();
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+  const ui::TouchEvent* event =
+      static_cast<const ui::TouchEvent*>(native_event);
+  DCHECK(event->IsTouchEvent());
+  return event->radius_x();
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+  const ui::TouchEvent* event =
+      static_cast<const ui::TouchEvent*>(native_event);
+  DCHECK(event->IsTouchEvent());
+  return event->radius_y();
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+  const ui::TouchEvent* event =
+      static_cast<const ui::TouchEvent*>(native_event);
+  DCHECK(event->IsTouchEvent());
+  return event->rotation_angle();
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+  const ui::TouchEvent* event =
+      static_cast<const ui::TouchEvent*>(native_event);
+  DCHECK(event->IsTouchEvent());
+  return event->force();
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+                      float* x_offset,
+                      float* y_offset,
+                      float* x_offset_ordinal,
+                      float* y_offset_ordinal,
+                      int* finger_count) {
+  const ui::ScrollEvent* event =
+      static_cast<const ui::ScrollEvent*>(native_event);
+  DCHECK(event->IsScrollEvent());
+  if (x_offset)
+    *x_offset = event->x_offset();
+  if (y_offset)
+    *y_offset = event->y_offset();
+  if (x_offset_ordinal)
+    *x_offset_ordinal = event->x_offset_ordinal();
+  if (y_offset_ordinal)
+    *y_offset_ordinal = event->y_offset_ordinal();
+  if (finger_count)
+    *finger_count = event->finger_count();
+
+  return true;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+                  float* vx,
+                  float* vy,
+                  float* vx_ordinal,
+                  float* vy_ordinal,
+                  bool* is_cancel) {
+  const ui::ScrollEvent* event =
+      static_cast<const ui::ScrollEvent*>(native_event);
+  DCHECK(event->IsScrollEvent());
+  if (vx)
+    *vx = event->x_offset();
+  if (vy)
+    *vy = event->y_offset();
+  if (vx_ordinal)
+    *vx_ordinal = event->x_offset_ordinal();
+  if (vy_ordinal)
+    *vy_ordinal = event->y_offset_ordinal();
+  if (is_cancel)
+    *is_cancel = event->type() == ET_SCROLL_FLING_CANCEL;
+
+  return true;
+}
+
+int GetModifiersFromKeyState() {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+void DispatchEventFromNativeUiEvent(const base::NativeEvent& native_event,
+                                    base::Callback<void(ui::Event*)> callback) {
+  const ui::Event* native_ui_event = static_cast<ui::Event*>(native_event);
+  if (native_ui_event->IsKeyEvent()) {
+    ui::KeyEvent key_event(native_event);
+    callback.Run(&key_event);
+  } else if (native_ui_event->IsMouseEvent()) {
+    ui::MouseEvent mouse_event(native_event);
+    callback.Run(&mouse_event);
+  } else if (native_ui_event->IsTouchEvent()) {
+    ui::TouchEvent touch_event(native_event);
+    callback.Run(&touch_event);
+  } else if (native_ui_event->IsScrollEvent()) {
+    ui::ScrollEvent scroll_event(native_event);
+    callback.Run(&scroll_event);
+  } else {
+    NOTREACHED();
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/ozone/events_ozone.gyp b/ui/events/ozone/events_ozone.gyp
new file mode 100644
index 0000000..1c88263
--- /dev/null
+++ b/ui/events/ozone/events_ozone.gyp
@@ -0,0 +1,98 @@
+# Copyright 2014 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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [{
+    'target_name': 'events_ozone',
+    'type': '<(component)',
+    'dependencies': [
+      '../../../base/base.gyp:base',
+    ],
+    'defines': [
+      'EVENTS_OZONE_IMPLEMENTATION',
+    ],
+    'sources': [
+      'device/device_event.cc',
+      'device/device_event.h',
+      'device/device_event_observer.h',
+      'device/device_manager.cc',
+      'device/device_manager.h',
+      'device/device_manager_manual.cc',
+      'device/device_manager_manual.h',
+      'device/udev/device_manager_udev.cc',
+      'device/udev/device_manager_udev.h',
+      'events_ozone_export.h',
+    ],
+    'conditions': [
+      ['use_udev==0', {
+        'sources/': [
+          ['exclude', '_udev\\.(h|cc)$'],
+        ],
+      }],
+      ['use_ozone_evdev==1 and use_udev==1', {
+        'dependencies': [
+          '<(DEPTH)/device/udev_linux/udev.gyp:udev_linux',
+        ],
+      }],
+    ],
+  }, {
+    'target_name': 'events_ozone_evdev',
+    'type': '<(component)',
+    'dependencies': [
+      '../../../base/base.gyp:base',
+      '../../gfx/gfx.gyp:gfx',
+      '../../ozone/ozone.gyp:ozone_base',
+      '../events.gyp:dom4_keycode_converter',
+      '../platform/events_platform.gyp:events_platform',
+      'events_ozone',
+    ],
+    'defines': [
+      'EVENTS_OZONE_EVDEV_IMPLEMENTATION',
+    ],
+    'sources': [
+      'evdev/libgestures_glue/event_reader_libevdev_cros.cc',
+      'evdev/libgestures_glue/event_reader_libevdev_cros.h',
+      'evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc',
+      'evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h',
+      'evdev/libgestures_glue/gesture_logging.cc',
+      'evdev/libgestures_glue/gesture_logging.h',
+      'evdev/libgestures_glue/gesture_timer_provider.cc',
+      'evdev/libgestures_glue/gesture_timer_provider.h',
+      'evdev/event_converter_evdev.cc',
+      'evdev/event_converter_evdev.h',
+      'evdev/event_device_info.cc',
+      'evdev/event_device_info.h',
+      'evdev/event_factory_evdev.cc',
+      'evdev/event_factory_evdev.h',
+      'evdev/event_modifiers_evdev.cc',
+      'evdev/event_modifiers_evdev.h',
+      'evdev/events_ozone_evdev_export.h',
+      'evdev/key_event_converter_evdev.cc',
+      'evdev/key_event_converter_evdev.h',
+      'evdev/touch_event_converter_evdev.cc',
+      'evdev/touch_event_converter_evdev.h',
+    ],
+    'conditions': [
+      ['use_ozone_evdev==1 and use_evdev_gestures==1', {
+        'dependencies': [
+          '<(DEPTH)/build/linux/system.gyp:libgestures',
+          '<(DEPTH)/build/linux/system.gyp:libevdev-cros',
+        ],
+        'defines': [
+          'USE_EVDEV_GESTURES',
+        ],
+      }, {
+        'sources/': [
+          ['exclude', '^evdev/libgestures_glue/'],
+        ],
+      }],
+      ['use_ozone_evdev==1', {
+        'defines': ['USE_OZONE_EVDEV=1'],
+      }],
+    ],
+  }]
+}
diff --git a/ui/events/ozone/events_ozone.h b/ui/events/ozone/events_ozone.h
new file mode 100644
index 0000000..ad603cd
--- /dev/null
+++ b/ui/events/ozone/events_ozone.h
@@ -0,0 +1,39 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVENTS_OZONE_H_
+#define UI_EVENTS_OZONE_EVENTS_OZONE_H_
+
+#include "base/callback.h"
+#include "base/event_types.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class Event;
+
+// Wrap a "native" ui::Event in another ui::Event & dispatch it.
+//
+// This is really unfortunate, but exists for two reasons:
+//
+//   1. Some of the ui::Event constructors depend on global state that
+//   is only used when building from a "native" event. For example:
+//   last_click_event_ is used when constructing MouseEvent from
+//   NativeEvent to determine click count.
+//
+//   2. Events contain a reference to a "native event", which some code
+//   depends on. The ui::Event might get mutated during dispatch, but
+//   the native event won't. Some code depends on the fact that the
+//   "native" version of the event is unmodified.
+//
+// We are trying to fix both of these issues, but in the meantime we
+// define NativeEvent == ui::Event.
+//
+EVENTS_EXPORT void DispatchEventFromNativeUiEvent(
+    const base::NativeEvent& native_event,
+    base::Callback<void(ui::Event*)> callback);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_OZONE_EVENTS_OZONE_H_
diff --git a/ui/events/ozone/events_ozone_export.h b/ui/events/ozone/events_ozone_export.h
new file mode 100644
index 0000000..bda8814
--- /dev/null
+++ b/ui/events/ozone/events_ozone_export.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 UI_EVENTS_OZONE_EVENTS_OZONE_EXPORT_H_
+#define UI_EVENTS_OZONE_EVENTS_OZONE_EXPORT_H_
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(EVENTS_OZONE_IMPLEMENTATION)
+#define EVENTS_OZONE_EXPORT __declspec(dllexport)
+#else
+#define EVENTS_OZONE_EXPORT __declspec(dllimport)
+#endif  // defined(EVENTS_OZONE_IMPLEMENTATION)
+
+#else  // defined(WIN32)
+#if defined(EVENTS_OZONE_IMPLEMENTATION)
+#define EVENTS_OZONE_EXPORT __attribute__((visibility("default")))
+#else
+#define EVENTS_OZONE_EXPORT
+#endif
+#endif
+
+#else  // defined(COMPONENT_BUILD)
+#define EVENTS_OZONE_EXPORT
+#endif
+
+#endif  // UI_EVENTS_OZONE_EVENTS_OZONE_EXPORT_H_
diff --git a/ui/events/platform/BUILD.gn b/ui/events/platform/BUILD.gn
new file mode 100644
index 0000000..4008a23
--- /dev/null
+++ b/ui/events/platform/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2014 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.
+
+import("//build/config/ui.gni")
+
+component("platform") {
+  sources = [
+    # Allow this target to include events_export.h without depending on the
+    # events target (which would be circular).
+    "../events_export.h",
+    "platform_event_dispatcher.h",
+    "platform_event_observer.h",
+    "platform_event_source.cc",
+    "platform_event_source.h",
+    "platform_event_source_stub.cc",
+    "platform_event_types.h",
+    "scoped_event_dispatcher.cc",
+    "scoped_event_dispatcher.h",
+  ]
+
+  defines = [
+    "EVENTS_IMPLEMENTATION",
+  ]
+
+  deps = [
+    "//base",
+  ]
+
+  if (use_x11) {
+    sources -= [
+      "platform_event_source_stub.cc",
+    ]
+  }
+}
diff --git a/ui/events/platform/events_platform.gyp b/ui/events/platform/events_platform.gyp
new file mode 100644
index 0000000..d1b3f6b
--- /dev/null
+++ b/ui/events/platform/events_platform.gyp
@@ -0,0 +1,36 @@
+# Copyright 2014 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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [{
+    'target_name': 'events_platform',
+    'type': '<(component)',
+    'dependencies': [
+      '../../../base/base.gyp:base',
+    ],
+    'defines': [
+      'EVENTS_IMPLEMENTATION',
+    ],
+    'sources': [
+      'platform_event_dispatcher.h',
+      'platform_event_observer.h',
+      'platform_event_source.cc',
+      'platform_event_source.h',
+      'platform_event_source_stub.cc',
+      'platform_event_types.h',
+      'scoped_event_dispatcher.cc',
+      'scoped_event_dispatcher.h',
+    ],
+    'conditions': [
+      ['use_x11==1', {
+        'sources!': [
+          'platform_event_source_stub.cc',
+        ],
+      }],
+    ],
+  }],
+}
diff --git a/ui/events/platform/platform_event_dispatcher.h b/ui/events/platform/platform_event_dispatcher.h
new file mode 100644
index 0000000..bda035d
--- /dev/null
+++ b/ui/events/platform/platform_event_dispatcher.h
@@ -0,0 +1,43 @@
+// Copyright 2014 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 UI_EVENTS_PLATFORM_PLATFORM_EVENT_DISPATCHER_H_
+#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_DISPATCHER_H_
+
+#include "base/basictypes.h"
+#include "ui/events/events_export.h"
+#include "ui/events/platform/platform_event_types.h"
+
+namespace ui {
+
+// See documentation for |PlatformEventDispatcher::DispatchEvent()| for
+// explanation of the meaning of the flags.
+enum PostDispatchAction {
+  POST_DISPATCH_NONE = 0x0,
+  POST_DISPATCH_PERFORM_DEFAULT = 0x1,
+  POST_DISPATCH_STOP_PROPAGATION = 0x2,
+};
+
+// PlatformEventDispatcher receives events from a PlatformEventSource and
+// dispatches them.
+class EVENTS_EXPORT PlatformEventDispatcher {
+ public:
+  // Returns whether this dispatcher wants to dispatch |event|.
+  virtual bool CanDispatchEvent(const PlatformEvent& event) = 0;
+
+  // Dispatches |event|. If this is not the default dispatcher, then the
+  // dispatcher can request that the default dispatcher gets a chance to
+  // dispatch the event by setting POST_DISPATCH_PERFORM_DEFAULT to the return
+  // value. If the dispatcher has processed the event, and no other dispatcher
+  // should be allowed to dispatch the event, then the dispatcher should set
+  // POST_DISPATCH_STOP_PROPAGATION flag on the return value.
+  virtual uint32_t DispatchEvent(const PlatformEvent& event) = 0;
+
+ protected:
+  virtual ~PlatformEventDispatcher() {}
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_PLATFORM_PLATFORM_EVENT_DISPATCHER_H_
diff --git a/ui/events/platform/platform_event_observer.h b/ui/events/platform/platform_event_observer.h
new file mode 100644
index 0000000..37aec63
--- /dev/null
+++ b/ui/events/platform/platform_event_observer.h
@@ -0,0 +1,29 @@
+// Copyright 2014 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 UI_EVENTS_PLATFORM_PLATFORM_EVENT_OBSERVER_H_
+#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_OBSERVER_H_
+
+#include "ui/events/events_export.h"
+#include "ui/events/platform/platform_event_types.h"
+
+namespace ui {
+
+// PlatformEventObserver can be installed on a PlatformEventSource, and it
+// receives all events that are dispatched to the dispatchers.
+class EVENTS_EXPORT PlatformEventObserver {
+ public:
+  // This is called before the dispatcher receives the event.
+  virtual void WillProcessEvent(const PlatformEvent& event) = 0;
+
+  // This is called after the event has been dispatched to the dispatcher(s).
+  virtual void DidProcessEvent(const PlatformEvent& event) = 0;
+
+ protected:
+  virtual ~PlatformEventObserver() {}
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_PLATFORM_PLATFORM_EVENT_OBSERVER_H_
diff --git a/ui/events/platform/platform_event_source.cc b/ui/events/platform/platform_event_source.cc
new file mode 100644
index 0000000..9607ab7
--- /dev/null
+++ b/ui/events/platform/platform_event_source.cc
@@ -0,0 +1,111 @@
+// Copyright 2014 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 "ui/events/platform/platform_event_source.h"
+
+#include <algorithm>
+
+#include "base/message_loop/message_loop.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_observer.h"
+#include "ui/events/platform/scoped_event_dispatcher.h"
+
+namespace ui {
+
+// static
+PlatformEventSource* PlatformEventSource::instance_ = NULL;
+
+PlatformEventSource::PlatformEventSource()
+    : overridden_dispatcher_(NULL),
+      overridden_dispatcher_restored_(false) {
+  CHECK(!instance_) << "Only one platform event source can be created.";
+  instance_ = this;
+}
+
+PlatformEventSource::~PlatformEventSource() {
+  CHECK_EQ(this, instance_);
+  instance_ = NULL;
+}
+
+PlatformEventSource* PlatformEventSource::GetInstance() { return instance_; }
+
+void PlatformEventSource::AddPlatformEventDispatcher(
+    PlatformEventDispatcher* dispatcher) {
+  CHECK(dispatcher);
+  dispatchers_.AddObserver(dispatcher);
+  OnDispatcherListChanged();
+}
+
+void PlatformEventSource::RemovePlatformEventDispatcher(
+    PlatformEventDispatcher* dispatcher) {
+  dispatchers_.RemoveObserver(dispatcher);
+  OnDispatcherListChanged();
+}
+
+scoped_ptr<ScopedEventDispatcher> PlatformEventSource::OverrideDispatcher(
+    PlatformEventDispatcher* dispatcher) {
+  CHECK(dispatcher);
+  overridden_dispatcher_restored_ = false;
+  return make_scoped_ptr(
+      new ScopedEventDispatcher(&overridden_dispatcher_, dispatcher));
+}
+
+void PlatformEventSource::AddPlatformEventObserver(
+    PlatformEventObserver* observer) {
+  CHECK(observer);
+  observers_.AddObserver(observer);
+}
+
+void PlatformEventSource::RemovePlatformEventObserver(
+    PlatformEventObserver* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+uint32_t PlatformEventSource::DispatchEvent(PlatformEvent platform_event) {
+  uint32_t action = POST_DISPATCH_PERFORM_DEFAULT;
+
+  FOR_EACH_OBSERVER(PlatformEventObserver, observers_,
+                    WillProcessEvent(platform_event));
+  // Give the overridden dispatcher a chance to dispatch the event first.
+  if (overridden_dispatcher_)
+    action = overridden_dispatcher_->DispatchEvent(platform_event);
+
+  if ((action & POST_DISPATCH_PERFORM_DEFAULT) &&
+      dispatchers_.might_have_observers()) {
+    ObserverList<PlatformEventDispatcher>::Iterator iter(dispatchers_);
+    while (PlatformEventDispatcher* dispatcher = iter.GetNext()) {
+      if (dispatcher->CanDispatchEvent(platform_event))
+        action = dispatcher->DispatchEvent(platform_event);
+      if (action & POST_DISPATCH_STOP_PROPAGATION)
+        break;
+    }
+  }
+  FOR_EACH_OBSERVER(PlatformEventObserver, observers_,
+                    DidProcessEvent(platform_event));
+
+  // If an overridden dispatcher has been destroyed, then the platform
+  // event-source should halt dispatching the current stream of events, and wait
+  // until the next message-loop iteration for dispatching events. This lets any
+  // nested message-loop to unwind correctly and any new dispatchers to receive
+  // the correct sequence of events.
+  if (overridden_dispatcher_restored_)
+    StopCurrentEventStream();
+
+  overridden_dispatcher_restored_ = false;
+
+  return action;
+}
+
+void PlatformEventSource::StopCurrentEventStream() {
+}
+
+void PlatformEventSource::OnDispatcherListChanged() {
+}
+
+void PlatformEventSource::OnOverriddenDispatcherRestored() {
+  CHECK(overridden_dispatcher_);
+  overridden_dispatcher_restored_ = true;
+}
+
+}  // namespace ui
diff --git a/ui/events/platform/platform_event_source.h b/ui/events/platform/platform_event_source.h
new file mode 100644
index 0000000..87b5531
--- /dev/null
+++ b/ui/events/platform/platform_event_source.h
@@ -0,0 +1,102 @@
+// Copyright 2014 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 UI_EVENTS_PLATFORM_PLATFORM_EVENT_SOURCE_H_
+#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_SOURCE_H_
+
+#include <map>
+#include <vector>
+
+#include "base/auto_reset.h"
+#include "base/macros.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/events/events_export.h"
+#include "ui/events/platform/platform_event_types.h"
+
+namespace ui {
+
+class Event;
+class PlatformEventDispatcher;
+class PlatformEventObserver;
+class ScopedEventDispatcher;
+
+// PlatformEventSource receives events from a source and dispatches the events
+// to the appropriate dispatchers.
+class EVENTS_EXPORT PlatformEventSource {
+ public:
+  virtual ~PlatformEventSource();
+
+  static PlatformEventSource* GetInstance();
+
+  // Adds a dispatcher to the dispatcher list. If a dispatcher is added during
+  // dispatching an event, then the newly added dispatcher also receives that
+  // event.
+  void AddPlatformEventDispatcher(PlatformEventDispatcher* dispatcher);
+
+  // Removes a dispatcher from the dispatcher list. Dispatchers can safely be
+  // removed from the dispatcher list during an event is being dispatched,
+  // without affecting the dispatch of the event to other existing dispatchers.
+  void RemovePlatformEventDispatcher(PlatformEventDispatcher* dispatcher);
+
+  // Installs a PlatformEventDispatcher that receives all the events. The
+  // dispatcher can process the event, or request that the default dispatchers
+  // be invoked by setting |POST_DISPATCH_PERFORM_DEFAULT| flag from the
+  // |DispatchEvent()| override.
+  // The returned |ScopedEventDispatcher| object is a handler for the overridden
+  // dispatcher. When this handler is destroyed, it removes the overridden
+  // dispatcher, and restores the previous override-dispatcher (or NULL if there
+  // wasn't any).
+  scoped_ptr<ScopedEventDispatcher> OverrideDispatcher(
+      PlatformEventDispatcher* dispatcher);
+
+  void AddPlatformEventObserver(PlatformEventObserver* observer);
+  void RemovePlatformEventObserver(PlatformEventObserver* observer);
+
+  static scoped_ptr<PlatformEventSource> CreateDefault();
+
+ protected:
+  PlatformEventSource();
+
+  // Dispatches |platform_event| to the dispatchers. If there is an override
+  // dispatcher installed using |OverrideDispatcher()|, then that dispatcher
+  // receives the event first. |POST_DISPATCH_QUIT_LOOP| flag is set in the
+  // returned value if the event-source should stop dispatching events at the
+  // current message-loop iteration.
+  virtual uint32_t DispatchEvent(PlatformEvent platform_event);
+
+ private:
+  friend class ScopedEventDispatcher;
+  static PlatformEventSource* instance_;
+
+  // Called to indicate that the source should stop dispatching the current
+  // stream of events and wait until the next iteration of the message-loop to
+  // dispatch the rest of the events.
+  virtual void StopCurrentEventStream();
+
+  // This is invoked when the list of dispatchers changes (i.e. a new dispatcher
+  // is added, or a dispatcher is removed).
+  virtual void OnDispatcherListChanged();
+
+  void OnOverriddenDispatcherRestored();
+
+  // Use an ObserverList<> instead of an std::vector<> to store the list of
+  // dispatchers, so that adding/removing dispatchers during an event dispatch
+  // is well-defined.
+  typedef ObserverList<PlatformEventDispatcher> PlatformEventDispatcherList;
+  PlatformEventDispatcherList dispatchers_;
+  PlatformEventDispatcher* overridden_dispatcher_;
+
+  // Used to keep track of whether the current override-dispatcher has been
+  // reset and a previous override-dispatcher has been restored.
+  bool overridden_dispatcher_restored_;
+
+  ObserverList<PlatformEventObserver> observers_;
+
+  DISALLOW_COPY_AND_ASSIGN(PlatformEventSource);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_PLATFORM_PLATFORM_EVENT_SOURCE_H_
diff --git a/ui/events/platform/platform_event_source_stub.cc b/ui/events/platform/platform_event_source_stub.cc
new file mode 100644
index 0000000..57e2a61
--- /dev/null
+++ b/ui/events/platform/platform_event_source_stub.cc
@@ -0,0 +1,13 @@
+// Copyright 2014 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 "ui/events/platform/platform_event_source.h"
+
+namespace ui {
+
+scoped_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() {
+  return scoped_ptr<PlatformEventSource>();
+}
+
+}  // namespace ui
diff --git a/ui/events/platform/platform_event_source_unittest.cc b/ui/events/platform/platform_event_source_unittest.cc
new file mode 100644
index 0000000..cdbd0c4
--- /dev/null
+++ b/ui/events/platform/platform_event_source_unittest.cc
@@ -0,0 +1,787 @@
+// Copyright 2014 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 "ui/events/platform/platform_event_source.h"
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/platform/platform_event_observer.h"
+#include "ui/events/platform/scoped_event_dispatcher.h"
+
+namespace ui {
+
+namespace {
+
+scoped_ptr<PlatformEvent> CreatePlatformEvent() {
+  scoped_ptr<PlatformEvent> event(new PlatformEvent());
+  memset(event.get(), 0, sizeof(PlatformEvent));
+  return event.Pass();
+}
+
+template <typename T>
+void DestroyScopedPtr(scoped_ptr<T> object) {}
+
+void RemoveDispatcher(PlatformEventDispatcher* dispatcher) {
+  PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(dispatcher);
+}
+
+void RemoveDispatchers(PlatformEventDispatcher* first,
+                       PlatformEventDispatcher* second) {
+  PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(first);
+  PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(second);
+}
+
+void AddDispatcher(PlatformEventDispatcher* dispatcher) {
+  PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(dispatcher);
+}
+
+}  // namespace
+
+class TestPlatformEventSource : public PlatformEventSource {
+ public:
+  TestPlatformEventSource()
+      : stop_stream_(false) {
+  }
+  virtual ~TestPlatformEventSource() {}
+
+  uint32_t Dispatch(const PlatformEvent& event) { return DispatchEvent(event); }
+
+  // Dispatches the stream of events, and returns the number of events that are
+  // dispatched before it is requested to stop.
+  size_t DispatchEventStream(const ScopedVector<PlatformEvent>& events) {
+    stop_stream_ = false;
+    for (size_t count = 0; count < events.size(); ++count) {
+      DispatchEvent(*events[count]);
+      if (stop_stream_)
+        return count + 1;
+    }
+    return events.size();
+  }
+
+  // PlatformEventSource:
+  virtual void StopCurrentEventStream() OVERRIDE {
+    stop_stream_ = true;
+  }
+
+ private:
+  bool stop_stream_;
+  DISALLOW_COPY_AND_ASSIGN(TestPlatformEventSource);
+};
+
+class TestPlatformEventDispatcher : public PlatformEventDispatcher {
+ public:
+  TestPlatformEventDispatcher(int id, std::vector<int>* list)
+      : id_(id),
+        list_(list),
+        post_dispatch_action_(POST_DISPATCH_NONE),
+        stop_stream_(false) {
+    PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
+  }
+  virtual ~TestPlatformEventDispatcher() {
+    PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
+  }
+
+  void set_post_dispatch_action(uint32_t action) {
+    post_dispatch_action_ = action;
+  }
+
+ protected:
+  // PlatformEventDispatcher:
+  virtual bool CanDispatchEvent(const PlatformEvent& event) OVERRIDE {
+    return true;
+  }
+
+  virtual uint32_t DispatchEvent(const PlatformEvent& event) OVERRIDE {
+    list_->push_back(id_);
+    return post_dispatch_action_;
+  }
+
+ private:
+  int id_;
+  std::vector<int>* list_;
+  uint32_t post_dispatch_action_;
+  bool stop_stream_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestPlatformEventDispatcher);
+};
+
+class TestPlatformEventObserver : public PlatformEventObserver {
+ public:
+  TestPlatformEventObserver(int id, std::vector<int>* list)
+      : id_(id), list_(list) {
+    PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
+  }
+  virtual ~TestPlatformEventObserver() {
+    PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
+  }
+
+ protected:
+  // PlatformEventObserver:
+  virtual void WillProcessEvent(const PlatformEvent& event) OVERRIDE {
+    list_->push_back(id_);
+  }
+
+  virtual void DidProcessEvent(const PlatformEvent& event) OVERRIDE {}
+
+ private:
+  int id_;
+  std::vector<int>* list_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestPlatformEventObserver);
+};
+
+class PlatformEventTest : public testing::Test {
+ public:
+  PlatformEventTest() {}
+  virtual ~PlatformEventTest() {}
+
+  TestPlatformEventSource* source() { return source_.get(); }
+
+ protected:
+  // testing::Test:
+  virtual void SetUp() OVERRIDE {
+    source_.reset(new TestPlatformEventSource());
+  }
+
+ private:
+  scoped_ptr<TestPlatformEventSource> source_;
+
+  DISALLOW_COPY_AND_ASSIGN(PlatformEventTest);
+};
+
+// Tests that a dispatcher receives an event.
+TEST_F(PlatformEventTest, DispatcherBasic) {
+  std::vector<int> list_dispatcher;
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  EXPECT_EQ(0u, list_dispatcher.size());
+  {
+    TestPlatformEventDispatcher dispatcher(1, &list_dispatcher);
+
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    source()->Dispatch(*event);
+    ASSERT_EQ(1u, list_dispatcher.size());
+    EXPECT_EQ(1, list_dispatcher[0]);
+  }
+
+  list_dispatcher.clear();
+  event = CreatePlatformEvent();
+  source()->Dispatch(*event);
+  EXPECT_EQ(0u, list_dispatcher.size());
+}
+
+// Tests that dispatchers receive events in the correct order.
+TEST_F(PlatformEventTest, DispatcherOrder) {
+  std::vector<int> list_dispatcher;
+  int sequence[] = {21, 3, 6, 45};
+  ScopedVector<TestPlatformEventDispatcher> dispatchers;
+  for (size_t i = 0; i < arraysize(sequence); ++i) {
+    dispatchers.push_back(
+        new TestPlatformEventDispatcher(sequence[i], &list_dispatcher));
+  }
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  ASSERT_EQ(arraysize(sequence), list_dispatcher.size());
+  EXPECT_EQ(std::vector<int>(sequence, sequence + arraysize(sequence)),
+            list_dispatcher);
+}
+
+// Tests that if a dispatcher consumes the event, the subsequent dispatchers do
+// not receive the event.
+TEST_F(PlatformEventTest, DispatcherConsumesEventToStopDispatch) {
+  std::vector<int> list_dispatcher;
+  TestPlatformEventDispatcher first(12, &list_dispatcher);
+  TestPlatformEventDispatcher second(23, &list_dispatcher);
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  ASSERT_EQ(2u, list_dispatcher.size());
+  EXPECT_EQ(12, list_dispatcher[0]);
+  EXPECT_EQ(23, list_dispatcher[1]);
+  list_dispatcher.clear();
+
+  first.set_post_dispatch_action(POST_DISPATCH_STOP_PROPAGATION);
+  event = CreatePlatformEvent();
+  source()->Dispatch(*event);
+  ASSERT_EQ(1u, list_dispatcher.size());
+  EXPECT_EQ(12, list_dispatcher[0]);
+}
+
+// Tests that observers receive events.
+TEST_F(PlatformEventTest, ObserverBasic) {
+  std::vector<int> list_observer;
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  EXPECT_EQ(0u, list_observer.size());
+  {
+    TestPlatformEventObserver observer(31, &list_observer);
+
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    source()->Dispatch(*event);
+    ASSERT_EQ(1u, list_observer.size());
+    EXPECT_EQ(31, list_observer[0]);
+  }
+
+  list_observer.clear();
+  event = CreatePlatformEvent();
+  source()->Dispatch(*event);
+  EXPECT_EQ(0u, list_observer.size());
+}
+
+// Tests that observers receive events in the correct order.
+TEST_F(PlatformEventTest, ObserverOrder) {
+  std::vector<int> list_observer;
+  const int sequence[] = {21, 3, 6, 45};
+  ScopedVector<TestPlatformEventObserver> observers;
+  for (size_t i = 0; i < arraysize(sequence); ++i) {
+    observers.push_back(
+        new TestPlatformEventObserver(sequence[i], &list_observer));
+  }
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  ASSERT_EQ(arraysize(sequence), list_observer.size());
+  EXPECT_EQ(std::vector<int>(sequence, sequence + arraysize(sequence)),
+            list_observer);
+}
+
+// Tests that observers and dispatchers receive events in the correct order.
+TEST_F(PlatformEventTest, DispatcherAndObserverOrder) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first_d(12, &list);
+  TestPlatformEventObserver first_o(10, &list);
+  TestPlatformEventDispatcher second_d(23, &list);
+  TestPlatformEventObserver second_o(20, &list);
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  const int expected[] = {10, 20, 12, 23};
+  EXPECT_EQ(std::vector<int>(expected, expected + arraysize(expected)), list);
+}
+
+// Tests that an overridden dispatcher receives events before the default
+// dispatchers.
+TEST_F(PlatformEventTest, OverriddenDispatcherBasic) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher dispatcher(10, &list);
+  TestPlatformEventObserver observer(15, &list);
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  ASSERT_EQ(2u, list.size());
+  EXPECT_EQ(15, list[0]);
+  EXPECT_EQ(10, list[1]);
+  list.clear();
+
+  TestPlatformEventDispatcher overriding_dispatcher(20, &list);
+  source()->RemovePlatformEventDispatcher(&overriding_dispatcher);
+  scoped_ptr<ScopedEventDispatcher> handle =
+      source()->OverrideDispatcher(&overriding_dispatcher);
+  source()->Dispatch(*event);
+  ASSERT_EQ(2u, list.size());
+  EXPECT_EQ(15, list[0]);
+  EXPECT_EQ(20, list[1]);
+}
+
+// Tests that an overridden dispatcher can request that the default dispatchers
+// can dispatch the events.
+TEST_F(PlatformEventTest, OverriddenDispatcherInvokeDefaultDispatcher) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher dispatcher(10, &list);
+  TestPlatformEventObserver observer(15, &list);
+  TestPlatformEventDispatcher overriding_dispatcher(20, &list);
+  source()->RemovePlatformEventDispatcher(&overriding_dispatcher);
+  scoped_ptr<ScopedEventDispatcher> handle =
+      source()->OverrideDispatcher(&overriding_dispatcher);
+  overriding_dispatcher.set_post_dispatch_action(POST_DISPATCH_PERFORM_DEFAULT);
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  // First the observer, then the overriding dispatcher, then the default
+  // dispatcher.
+  ASSERT_EQ(3u, list.size());
+  EXPECT_EQ(15, list[0]);
+  EXPECT_EQ(20, list[1]);
+  EXPECT_EQ(10, list[2]);
+  list.clear();
+
+  // Install a second overriding dispatcher.
+  TestPlatformEventDispatcher second_overriding(50, &list);
+  source()->RemovePlatformEventDispatcher(&second_overriding);
+  scoped_ptr<ScopedEventDispatcher> second_override_handle =
+      source()->OverrideDispatcher(&second_overriding);
+  source()->Dispatch(*event);
+  ASSERT_EQ(2u, list.size());
+  EXPECT_EQ(15, list[0]);
+  EXPECT_EQ(50, list[1]);
+  list.clear();
+
+  second_overriding.set_post_dispatch_action(POST_DISPATCH_PERFORM_DEFAULT);
+  source()->Dispatch(*event);
+  // First the observer, then the second overriding dispatcher, then the default
+  // dispatcher.
+  ASSERT_EQ(3u, list.size());
+  EXPECT_EQ(15, list[0]);
+  EXPECT_EQ(50, list[1]);
+  EXPECT_EQ(10, list[2]);
+}
+
+// Runs a callback during an event dispatch.
+class RunCallbackDuringDispatch : public TestPlatformEventDispatcher {
+ public:
+  RunCallbackDuringDispatch(int id, std::vector<int>* list)
+      : TestPlatformEventDispatcher(id, list) {}
+  virtual ~RunCallbackDuringDispatch() {}
+
+  void set_callback(const base::Closure& callback) {
+    callback_ = callback;
+  }
+
+ protected:
+  // PlatformEventDispatcher:
+  virtual uint32_t DispatchEvent(const PlatformEvent& event) OVERRIDE {
+    if (!callback_.is_null())
+      callback_.Run();
+    return TestPlatformEventDispatcher::DispatchEvent(event);
+  }
+
+ private:
+  base::Closure callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(RunCallbackDuringDispatch);
+};
+
+// Test that if a dispatcher removes another dispatcher that is later in the
+// dispatcher list during dispatching an event, then event dispatching still
+// continues correctly.
+TEST_F(PlatformEventTest, DispatcherRemovesNextDispatcherDuringDispatch) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first(10, &list);
+  RunCallbackDuringDispatch second(15, &list);
+  TestPlatformEventDispatcher third(20, &list);
+  TestPlatformEventDispatcher fourth(30, &list);
+
+  second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&third)));
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  // |second| removes |third| from the dispatcher list during dispatch. So the
+  // event should only reach |first|, |second|, and |fourth|.
+  ASSERT_EQ(3u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+  EXPECT_EQ(30, list[2]);
+}
+
+// Tests that if a dispatcher removes itself from the dispatcher list during
+// dispatching an event, then event dispatching continues correctly.
+TEST_F(PlatformEventTest, DispatcherRemovesSelfDuringDispatch) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first(10, &list);
+  RunCallbackDuringDispatch second(15, &list);
+  TestPlatformEventDispatcher third(20, &list);
+
+  second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&second)));
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  // |second| removes itself from the dispatcher list during dispatch. So the
+  // event should reach all three dispatchers in the list.
+  ASSERT_EQ(3u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+  EXPECT_EQ(20, list[2]);
+}
+
+// Tests that if a dispatcher removes itself from the dispatcher list during
+// dispatching an event, and this dispatcher is last in the dispatcher-list,
+// then event dispatching ends correctly.
+TEST_F(PlatformEventTest, DispatcherRemovesSelfDuringDispatchLast) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first(10, &list);
+  RunCallbackDuringDispatch second(15, &list);
+
+  second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&second)));
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  // |second| removes itself during dispatch. So both dispatchers will have
+  // received the event.
+  ASSERT_EQ(2u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+}
+
+// Tests that if a dispatcher removes a single dispatcher that comes before it
+// in the dispatcher list, then dispatch continues correctly.
+TEST_F(PlatformEventTest, DispatcherRemovesPrevDispatcherDuringDispatch) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first(10, &list);
+  RunCallbackDuringDispatch second(15, &list);
+  TestPlatformEventDispatcher third(20, &list);
+
+  second.set_callback(base::Bind(&RemoveDispatcher, base::Unretained(&first)));
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  // |second| removes |first| from the dispatcher list during dispatch. The
+  // event should reach all three dispatchers.
+  ASSERT_EQ(3u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+  EXPECT_EQ(20, list[2]);
+}
+
+// Tests that if a dispatcher removes multiple dispatchers that comes before it
+// in the dispatcher list, then dispatch continues correctly.
+TEST_F(PlatformEventTest, DispatcherRemovesPrevDispatchersDuringDispatch) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first(10, &list);
+  TestPlatformEventDispatcher second(12, &list);
+  RunCallbackDuringDispatch third(15, &list);
+  TestPlatformEventDispatcher fourth(20, &list);
+
+  third.set_callback(base::Bind(&RemoveDispatchers,
+                                base::Unretained(&first),
+                                base::Unretained(&second)));
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  // |third| removes |first| and |second| from the dispatcher list during
+  // dispatch. The event should reach all three dispatchers.
+  ASSERT_EQ(4u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(12, list[1]);
+  EXPECT_EQ(15, list[2]);
+  EXPECT_EQ(20, list[3]);
+}
+
+// Tests that adding a dispatcher during dispatching an event receives that
+// event.
+TEST_F(PlatformEventTest, DispatcherAddedDuringDispatchReceivesEvent) {
+  std::vector<int> list;
+  TestPlatformEventDispatcher first(10, &list);
+  RunCallbackDuringDispatch second(15, &list);
+  TestPlatformEventDispatcher third(20, &list);
+  TestPlatformEventDispatcher fourth(30, &list);
+  RemoveDispatchers(&third, &fourth);
+
+  scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+  source()->Dispatch(*event);
+  ASSERT_EQ(2u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+
+  second.set_callback(base::Bind(&AddDispatcher, base::Unretained(&third)));
+  list.clear();
+  source()->Dispatch(*event);
+  ASSERT_EQ(3u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+  EXPECT_EQ(20, list[2]);
+
+  second.set_callback(base::Bind(&AddDispatcher, base::Unretained(&fourth)));
+  list.clear();
+  source()->Dispatch(*event);
+  ASSERT_EQ(4u, list.size());
+  EXPECT_EQ(10, list[0]);
+  EXPECT_EQ(15, list[1]);
+  EXPECT_EQ(20, list[2]);
+  EXPECT_EQ(30, list[3]);
+}
+
+// Provides mechanism for running tests from inside an active message-loop.
+class PlatformEventTestWithMessageLoop : public PlatformEventTest {
+ public:
+  PlatformEventTestWithMessageLoop() {}
+  virtual ~PlatformEventTestWithMessageLoop() {}
+
+  void Run() {
+    message_loop_.PostTask(
+        FROM_HERE,
+        base::Bind(&PlatformEventTestWithMessageLoop::RunTest,
+                   base::Unretained(this)));
+    message_loop_.Run();
+  }
+
+ protected:
+  void RunTest() {
+    RunTestImpl();
+    message_loop_.Quit();
+  }
+
+  virtual void RunTestImpl() = 0;
+
+ private:
+  base::MessageLoopForUI message_loop_;
+
+  DISALLOW_COPY_AND_ASSIGN(PlatformEventTestWithMessageLoop);
+};
+
+#define RUN_TEST_IN_MESSAGE_LOOP(name) \
+  TEST_F(name, Run) { Run(); }
+
+// Tests that a ScopedEventDispatcher restores the previous dispatcher when
+// destroyed.
+class ScopedDispatcherRestoresAfterDestroy
+    : public PlatformEventTestWithMessageLoop {
+ public:
+  // PlatformEventTestWithMessageLoop:
+  virtual void RunTestImpl() OVERRIDE {
+    std::vector<int> list;
+    TestPlatformEventDispatcher dispatcher(10, &list);
+    TestPlatformEventObserver observer(15, &list);
+
+    TestPlatformEventDispatcher first_overriding(20, &list);
+    source()->RemovePlatformEventDispatcher(&first_overriding);
+    scoped_ptr<ScopedEventDispatcher> first_override_handle =
+        source()->OverrideDispatcher(&first_overriding);
+
+    // Install a second overriding dispatcher.
+    TestPlatformEventDispatcher second_overriding(50, &list);
+    source()->RemovePlatformEventDispatcher(&second_overriding);
+    scoped_ptr<ScopedEventDispatcher> second_override_handle =
+        source()->OverrideDispatcher(&second_overriding);
+
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list.size());
+    EXPECT_EQ(15, list[0]);
+    EXPECT_EQ(50, list[1]);
+    list.clear();
+
+    second_override_handle.reset();
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list.size());
+    EXPECT_EQ(15, list[0]);
+    EXPECT_EQ(20, list[1]);
+  }
+};
+
+RUN_TEST_IN_MESSAGE_LOOP(ScopedDispatcherRestoresAfterDestroy)
+
+// This dispatcher destroys the handle to the ScopedEventDispatcher when
+// dispatching an event.
+class DestroyScopedHandleDispatcher : public TestPlatformEventDispatcher {
+ public:
+  DestroyScopedHandleDispatcher(int id, std::vector<int>* list)
+      : TestPlatformEventDispatcher(id, list) {}
+  virtual ~DestroyScopedHandleDispatcher() {}
+
+  void SetScopedHandle(scoped_ptr<ScopedEventDispatcher> handler) {
+    handler_ = handler.Pass();
+  }
+
+  void set_callback(const base::Closure& callback) {
+    callback_ = callback;
+  }
+
+ private:
+  // PlatformEventDispatcher:
+  virtual bool CanDispatchEvent(const PlatformEvent& event) OVERRIDE {
+    return true;
+  }
+
+  virtual uint32_t DispatchEvent(const PlatformEvent& event) OVERRIDE {
+    handler_.reset();
+    uint32_t action = TestPlatformEventDispatcher::DispatchEvent(event);
+    if (!callback_.is_null()) {
+      callback_.Run();
+      callback_ = base::Closure();
+    }
+    return action;
+  }
+
+  scoped_ptr<ScopedEventDispatcher> handler_;
+  base::Closure callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(DestroyScopedHandleDispatcher);
+};
+
+// Tests that resetting an overridden dispatcher causes the nested message-loop
+// iteration to stop and the rest of the events are dispatched in the next
+// iteration.
+class DestroyedNestedOverriddenDispatcherQuitsNestedLoopIteration
+    : public PlatformEventTestWithMessageLoop {
+ public:
+  void NestedTask(std::vector<int>* list,
+                  TestPlatformEventDispatcher* dispatcher) {
+    ScopedVector<PlatformEvent> events;
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    events.push_back(event.release());
+    event = CreatePlatformEvent();
+    events.push_back(event.release());
+
+    // Attempt to dispatch a couple of events. Dispatching the first event will
+    // have terminated the ScopedEventDispatcher object, which will terminate
+    // the current iteration of the message-loop.
+    size_t count = source()->DispatchEventStream(events);
+    EXPECT_EQ(1u, count);
+    ASSERT_EQ(2u, list->size());
+    EXPECT_EQ(15, (*list)[0]);
+    EXPECT_EQ(20, (*list)[1]);
+    list->clear();
+
+    ASSERT_LT(count, events.size());
+    events.erase(events.begin(), events.begin() + count);
+
+    count = source()->DispatchEventStream(events);
+    EXPECT_EQ(1u, count);
+    ASSERT_EQ(2u, list->size());
+    EXPECT_EQ(15, (*list)[0]);
+    EXPECT_EQ(10, (*list)[1]);
+    list->clear();
+
+    // Terminate the message-loop.
+    base::MessageLoopForUI::current()->QuitNow();
+  }
+
+  // PlatformEventTestWithMessageLoop:
+  virtual void RunTestImpl() OVERRIDE {
+    std::vector<int> list;
+    TestPlatformEventDispatcher dispatcher(10, &list);
+    TestPlatformEventObserver observer(15, &list);
+
+    DestroyScopedHandleDispatcher overriding(20, &list);
+    source()->RemovePlatformEventDispatcher(&overriding);
+    scoped_ptr<ScopedEventDispatcher> override_handle =
+        source()->OverrideDispatcher(&overriding);
+
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list.size());
+    EXPECT_EQ(15, list[0]);
+    EXPECT_EQ(20, list[1]);
+    list.clear();
+
+    overriding.SetScopedHandle(override_handle.Pass());
+    base::RunLoop run_loop;
+    base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
+    base::MessageLoopForUI::ScopedNestableTaskAllower allow_nested(loop);
+    loop->PostTask(
+        FROM_HERE,
+        base::Bind(
+            &DestroyedNestedOverriddenDispatcherQuitsNestedLoopIteration::
+                NestedTask,
+            base::Unretained(this),
+            base::Unretained(&list),
+            base::Unretained(&overriding)));
+    run_loop.Run();
+
+    // Dispatching the event should now reach the default dispatcher.
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list.size());
+    EXPECT_EQ(15, list[0]);
+    EXPECT_EQ(10, list[1]);
+  }
+};
+
+RUN_TEST_IN_MESSAGE_LOOP(
+    DestroyedNestedOverriddenDispatcherQuitsNestedLoopIteration)
+
+// Tests that resetting an overridden dispatcher, and installing another
+// overridden dispatcher before the nested message-loop completely unwinds
+// function correctly.
+class ConsecutiveOverriddenDispatcherInTheSameMessageLoopIteration
+    : public PlatformEventTestWithMessageLoop {
+ public:
+  void NestedTask(scoped_ptr<ScopedEventDispatcher> dispatch_handle,
+                  std::vector<int>* list) {
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list->size());
+    EXPECT_EQ(15, (*list)[0]);
+    EXPECT_EQ(20, (*list)[1]);
+    list->clear();
+
+    // Reset the override dispatcher. This should restore the default
+    // dispatcher.
+    dispatch_handle.reset();
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list->size());
+    EXPECT_EQ(15, (*list)[0]);
+    EXPECT_EQ(10, (*list)[1]);
+    list->clear();
+
+    // Install another override-dispatcher.
+    DestroyScopedHandleDispatcher second_overriding(70, list);
+    source()->RemovePlatformEventDispatcher(&second_overriding);
+    scoped_ptr<ScopedEventDispatcher> second_override_handle =
+        source()->OverrideDispatcher(&second_overriding);
+
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list->size());
+    EXPECT_EQ(15, (*list)[0]);
+    EXPECT_EQ(70, (*list)[1]);
+    list->clear();
+
+    second_overriding.SetScopedHandle(second_override_handle.Pass());
+    second_overriding.set_post_dispatch_action(POST_DISPATCH_NONE);
+    base::RunLoop run_loop;
+    second_overriding.set_callback(run_loop.QuitClosure());
+    base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
+    base::MessageLoopForUI::ScopedNestableTaskAllower allow_nested(loop);
+    loop->PostTask(
+        FROM_HERE,
+        base::Bind(base::IgnoreResult(&TestPlatformEventSource::Dispatch),
+                   base::Unretained(source()),
+                   *event));
+    run_loop.Run();
+    ASSERT_EQ(2u, list->size());
+    EXPECT_EQ(15, (*list)[0]);
+    EXPECT_EQ(70, (*list)[1]);
+    list->clear();
+
+    // Terminate the message-loop.
+    base::MessageLoopForUI::current()->QuitNow();
+  }
+
+  // PlatformEventTestWithMessageLoop:
+  virtual void RunTestImpl() OVERRIDE {
+    std::vector<int> list;
+    TestPlatformEventDispatcher dispatcher(10, &list);
+    TestPlatformEventObserver observer(15, &list);
+
+    TestPlatformEventDispatcher overriding(20, &list);
+    source()->RemovePlatformEventDispatcher(&overriding);
+    scoped_ptr<ScopedEventDispatcher> override_handle =
+        source()->OverrideDispatcher(&overriding);
+
+    scoped_ptr<PlatformEvent> event(CreatePlatformEvent());
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list.size());
+    EXPECT_EQ(15, list[0]);
+    EXPECT_EQ(20, list[1]);
+    list.clear();
+
+    // Start a nested message-loop, and destroy |override_handle| in the nested
+    // loop. That should terminate the nested loop, restore the previous
+    // dispatchers, and return control to this function.
+    base::RunLoop run_loop;
+    base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
+    base::MessageLoopForUI::ScopedNestableTaskAllower allow_nested(loop);
+    loop->PostTask(
+        FROM_HERE,
+        base::Bind(
+            &ConsecutiveOverriddenDispatcherInTheSameMessageLoopIteration::
+                NestedTask,
+            base::Unretained(this),
+            base::Passed(&override_handle),
+            base::Unretained(&list)));
+    run_loop.Run();
+
+    // Dispatching the event should now reach the default dispatcher.
+    source()->Dispatch(*event);
+    ASSERT_EQ(2u, list.size());
+    EXPECT_EQ(15, list[0]);
+    EXPECT_EQ(10, list[1]);
+  }
+};
+
+RUN_TEST_IN_MESSAGE_LOOP(
+    ConsecutiveOverriddenDispatcherInTheSameMessageLoopIteration)
+
+}  // namespace ui
diff --git a/ui/events/platform/platform_event_types.h b/ui/events/platform/platform_event_types.h
new file mode 100644
index 0000000..dedb38f
--- /dev/null
+++ b/ui/events/platform/platform_event_types.h
@@ -0,0 +1,14 @@
+// Copyright 2014 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 UI_EVENTS_PLATFORM_PLATFORM_EVENT_TYPES_H_
+#define UI_EVENTS_PLATFORM_PLATFORM_EVENT_TYPES_H_
+
+#include "base/event_types.h"
+
+namespace ui {
+typedef base::NativeEvent PlatformEvent;
+}  // namespace ui
+
+#endif  // UI_EVENTS_PLATFORM_PLATFORM_EVENT_TYPES_H_
diff --git a/ui/events/platform/scoped_event_dispatcher.cc b/ui/events/platform/scoped_event_dispatcher.cc
new file mode 100644
index 0000000..b0941ab
--- /dev/null
+++ b/ui/events/platform/scoped_event_dispatcher.cc
@@ -0,0 +1,21 @@
+// Copyright 2014 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 "ui/events/platform/scoped_event_dispatcher.h"
+
+#include "ui/events/platform/platform_event_source.h"
+
+namespace ui {
+
+ScopedEventDispatcher::ScopedEventDispatcher(
+    PlatformEventDispatcher** scoped_dispatcher,
+    PlatformEventDispatcher* new_dispatcher)
+    : original_(*scoped_dispatcher),
+      restore_(scoped_dispatcher, new_dispatcher) {}
+
+ScopedEventDispatcher::~ScopedEventDispatcher() {
+  PlatformEventSource::GetInstance()->OnOverriddenDispatcherRestored();
+}
+
+}  // namespace ui
diff --git a/ui/events/platform/scoped_event_dispatcher.h b/ui/events/platform/scoped_event_dispatcher.h
new file mode 100644
index 0000000..a2f5294
--- /dev/null
+++ b/ui/events/platform/scoped_event_dispatcher.h
@@ -0,0 +1,40 @@
+// Copyright 2014 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 UI_EVENTS_PLATFORM_SCOPED_EVENT_DISPATCHER_H_
+#define UI_EVENTS_PLATFORM_SCOPED_EVENT_DISPATCHER_H_
+
+#include "base/auto_reset.h"
+#include "base/basictypes.h"
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+class PlatformEventDispatcher;
+
+// A temporary PlatformEventDispatcher can be installed on a
+// PlatformEventSource that overrides all installed event dispatchers, and
+// always gets a chance to dispatch the event first. The PlatformEventSource
+// returns a ScopedEventDispatcher object in such cases. This
+// ScopedEventDispatcher object can be used to dispatch the event to any
+// previous overridden dispatcher. When this object is destroyed, it removes the
+// override-dispatcher, and restores the previous override-dispatcher.
+class EVENTS_EXPORT ScopedEventDispatcher {
+ public:
+  ScopedEventDispatcher(PlatformEventDispatcher** scoped_dispatcher,
+                        PlatformEventDispatcher* new_dispatcher);
+  ~ScopedEventDispatcher();
+
+  operator PlatformEventDispatcher*() const { return original_; }
+
+ private:
+  PlatformEventDispatcher* original_;
+  base::AutoReset<PlatformEventDispatcher*> restore_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatcher);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_PLATFORM_SCOPED_EVENT_DISPATCHER_H_
diff --git a/ui/events/platform/x11/BUILD.gn b/ui/events/platform/x11/BUILD.gn
new file mode 100644
index 0000000..3ed70b9
--- /dev/null
+++ b/ui/events/platform/x11/BUILD.gn
@@ -0,0 +1,33 @@
+# Copyright 2014 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.
+
+component("x11") {
+  output_name = "x11_events_platform"
+
+  sources = [
+    "x11_event_source.cc",
+    "x11_event_source.h",
+    "x11_event_source_glib.cc",
+    "x11_event_source_libevent.cc",
+  ]
+
+  defines = [ "EVENTS_IMPLEMENTATION" ]
+
+  configs += [ "//build/config/linux:x11" ]
+
+  public_deps = [
+    "//ui/events",
+    "//ui/events:events_base",
+    "//ui/events/platform",
+    "//ui/gfx/x",
+  ]
+
+  if (is_linux) {
+    sources -= [ "x11_event_source_libevent.cc" ]
+
+    configs += [ "//build/config/linux:glib" ]
+  } else {
+    sources -= [ "x11_event_source_glib.cc" ]
+  }
+}
diff --git a/ui/events/platform/x11/x11_event_source.cc b/ui/events/platform/x11/x11_event_source.cc
new file mode 100644
index 0000000..b2f5dfa
--- /dev/null
+++ b/ui/events/platform/x11/x11_event_source.cc
@@ -0,0 +1,158 @@
+// Copyright 2014 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 "ui/events/platform/x11/x11_event_source.h"
+
+#include <X11/extensions/XInput2.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+
+#include "base/logging.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/platform/platform_event_dispatcher.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/events/x/hotplug_event_handler_x11.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace ui {
+
+namespace {
+
+int g_xinput_opcode = -1;
+
+bool InitializeXInput2(XDisplay* display) {
+  if (!display)
+    return false;
+
+  int event, err;
+
+  int xiopcode;
+  if (!XQueryExtension(display, "XInputExtension", &xiopcode, &event, &err)) {
+    DVLOG(1) << "X Input extension not available.";
+    return false;
+  }
+  g_xinput_opcode = xiopcode;
+
+#if defined(USE_XI2_MT)
+  // USE_XI2_MT also defines the required XI2 minor minimum version.
+  int major = 2, minor = USE_XI2_MT;
+#else
+  int major = 2, minor = 0;
+#endif
+  if (XIQueryVersion(display, &major, &minor) == BadRequest) {
+    DVLOG(1) << "XInput2 not supported in the server.";
+    return false;
+  }
+#if defined(USE_XI2_MT)
+  if (major < 2 || (major == 2 && minor < USE_XI2_MT)) {
+    DVLOG(1) << "XI version on server is " << major << "." << minor << ". "
+            << "But 2." << USE_XI2_MT << " is required.";
+    return false;
+  }
+#endif
+
+  return true;
+}
+
+bool InitializeXkb(XDisplay* display) {
+  if (!display)
+    return false;
+
+  int opcode, event, error;
+  int major = XkbMajorVersion;
+  int minor = XkbMinorVersion;
+  if (!XkbQueryExtension(display, &opcode, &event, &error, &major, &minor)) {
+    DVLOG(1) << "Xkb extension not available.";
+    return false;
+  }
+
+  // Ask the server not to send KeyRelease event when the user holds down a key.
+  // crbug.com/138092
+  Bool supported_return;
+  if (!XkbSetDetectableAutoRepeat(display, True, &supported_return)) {
+    DVLOG(1) << "XKB not supported in the server.";
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+X11EventSource::X11EventSource(XDisplay* display)
+    : display_(display),
+      continue_stream_(true) {
+  CHECK(display_);
+  DeviceDataManagerX11::CreateInstance();
+  hotplug_event_handler_.reset(
+      new HotplugEventHandlerX11(DeviceDataManager::GetInstance()));
+  InitializeXInput2(display_);
+  InitializeXkb(display_);
+
+  // Force the initial device query to have an update list of active devices.
+  hotplug_event_handler_->OnHotplugEvent();
+}
+
+X11EventSource::~X11EventSource() {
+}
+
+// static
+X11EventSource* X11EventSource::GetInstance() {
+  return static_cast<X11EventSource*>(PlatformEventSource::GetInstance());
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// X11EventSource, public
+
+void X11EventSource::DispatchXEvents() {
+  DCHECK(display_);
+  // Handle all pending events.
+  // It may be useful to eventually align this event dispatch with vsync, but
+  // not yet.
+  continue_stream_ = true;
+  while (XPending(display_) && continue_stream_) {
+    XEvent xevent;
+    XNextEvent(display_, &xevent);
+    DispatchEvent(&xevent);
+  }
+}
+
+void X11EventSource::BlockUntilWindowMapped(XID window) {
+  XEvent event;
+  do {
+    // Block until there's a message of |event_mask| type on |w|. Then remove
+    // it from the queue and stuff it in |event|.
+    XWindowEvent(display_, window, StructureNotifyMask, &event);
+    DispatchEvent(&event);
+  } while (event.type != MapNotify);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// X11EventSource, private
+
+uint32_t X11EventSource::DispatchEvent(XEvent* xevent) {
+  bool have_cookie = false;
+  if (xevent->type == GenericEvent &&
+      XGetEventData(xevent->xgeneric.display, &xevent->xcookie)) {
+    have_cookie = true;
+  }
+
+  uint32_t action = PlatformEventSource::DispatchEvent(xevent);
+  if (xevent->type == GenericEvent &&
+      xevent->xgeneric.evtype == XI_HierarchyChanged) {
+    ui::UpdateDeviceList();
+    hotplug_event_handler_->OnHotplugEvent();
+  }
+
+  if (have_cookie)
+    XFreeEventData(xevent->xgeneric.display, &xevent->xcookie);
+  return action;
+}
+
+void X11EventSource::StopCurrentEventStream() {
+  continue_stream_ = false;
+}
+
+}  // namespace ui
diff --git a/ui/events/platform/x11/x11_event_source.h b/ui/events/platform/x11/x11_event_source.h
new file mode 100644
index 0000000..598afa8
--- /dev/null
+++ b/ui/events/platform/x11/x11_event_source.h
@@ -0,0 +1,68 @@
+// Copyright 2014 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 UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_
+#define UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/events_export.h"
+#include "ui/events/platform/platform_event_source.h"
+#include "ui/gfx/x/x11_types.h"
+
+typedef struct _GPollFD GPollFD;
+typedef struct _GSource GSource;
+typedef union _XEvent XEvent;
+typedef unsigned long XID;
+
+namespace ui {
+
+class HotplugEventHandlerX11;
+
+// A PlatformEventSource implementation for reading events from X11 server and
+// dispatching the events to the appropriate dispatcher.
+class EVENTS_EXPORT X11EventSource : public PlatformEventSource {
+ public:
+  explicit X11EventSource(XDisplay* display);
+  virtual ~X11EventSource();
+
+  static X11EventSource* GetInstance();
+
+  // Called by the glib source dispatch function. Processes all (if any)
+  // available X events.
+  void DispatchXEvents();
+
+  // Blocks on the X11 event queue until we receive notification from the
+  // xserver that |w| has been mapped; StructureNotifyMask events on |w| are
+  // pulled out from the queue and dispatched out of order.
+  //
+  // For those that know X11, this is really a wrapper around XWindowEvent
+  // which still makes sure the preempted event is dispatched instead of
+  // dropped on the floor. This method exists because mapping a window is
+  // asynchronous (and we receive an XEvent when mapped), while there are also
+  // functions which require a mapped window.
+  void BlockUntilWindowMapped(XID window);
+
+ protected:
+  XDisplay* display() { return display_; }
+
+ private:
+  // PlatformEventSource:
+  virtual uint32_t DispatchEvent(XEvent* xevent) OVERRIDE;
+  virtual void StopCurrentEventStream() OVERRIDE;
+
+  // The connection to the X11 server used to receive the events.
+  XDisplay* display_;
+
+  // Keeps track of whether this source should continue to dispatch all the
+  // available events.
+  bool continue_stream_;
+
+  scoped_ptr<HotplugEventHandlerX11> hotplug_event_handler_;
+
+  DISALLOW_COPY_AND_ASSIGN(X11EventSource);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_
diff --git a/ui/events/platform/x11/x11_event_source_glib.cc b/ui/events/platform/x11/x11_event_source_glib.cc
new file mode 100644
index 0000000..9504422
--- /dev/null
+++ b/ui/events/platform/x11/x11_event_source_glib.cc
@@ -0,0 +1,101 @@
+// Copyright 2014 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 "ui/events/platform/x11/x11_event_source.h"
+
+#include <glib.h>
+#include <X11/Xlib.h>
+
+namespace ui {
+
+namespace {
+
+struct GLibX11Source : public GSource {
+  // Note: The GLibX11Source is created and destroyed by GLib. So its
+  // constructor/destructor may or may not get called.
+  XDisplay* display;
+  GPollFD* poll_fd;
+};
+
+gboolean XSourcePrepare(GSource* source, gint* timeout_ms) {
+  GLibX11Source* gxsource = static_cast<GLibX11Source*>(source);
+  if (XPending(gxsource->display))
+    *timeout_ms = 0;
+  else
+    *timeout_ms = -1;
+  return FALSE;
+}
+
+gboolean XSourceCheck(GSource* source) {
+  GLibX11Source* gxsource = static_cast<GLibX11Source*>(source);
+  return XPending(gxsource->display);
+}
+
+gboolean XSourceDispatch(GSource* source,
+                         GSourceFunc unused_func,
+                         gpointer data) {
+  X11EventSource* x11_source = static_cast<X11EventSource*>(data);
+  x11_source->DispatchXEvents();
+  return TRUE;
+}
+
+GSourceFuncs XSourceFuncs = {
+  XSourcePrepare,
+  XSourceCheck,
+  XSourceDispatch,
+  NULL
+};
+
+class X11EventSourceGlib : public X11EventSource {
+ public:
+  explicit X11EventSourceGlib(XDisplay* display)
+      : X11EventSource(display),
+        x_source_(NULL) {
+    InitXSource(ConnectionNumber(display));
+  }
+
+  virtual ~X11EventSourceGlib() {
+    g_source_destroy(x_source_);
+    g_source_unref(x_source_);
+  }
+
+ private:
+  void InitXSource(int fd) {
+    CHECK(!x_source_);
+    CHECK(display()) << "Unable to get connection to X server";
+
+    x_poll_.reset(new GPollFD());
+    x_poll_->fd = fd;
+    x_poll_->events = G_IO_IN;
+    x_poll_->revents = 0;
+
+    GLibX11Source* glib_x_source = static_cast<GLibX11Source*>
+        (g_source_new(&XSourceFuncs, sizeof(GLibX11Source)));
+    glib_x_source->display = display();
+    glib_x_source->poll_fd = x_poll_.get();
+
+    x_source_ = glib_x_source;
+    g_source_add_poll(x_source_, x_poll_.get());
+    g_source_set_can_recurse(x_source_, TRUE);
+    g_source_set_callback(x_source_, NULL, this, NULL);
+    g_source_attach(x_source_, g_main_context_default());
+  }
+
+  // The GLib event source for X events.
+  GSource* x_source_;
+
+  // The poll attached to |x_source_|.
+  scoped_ptr<GPollFD> x_poll_;
+
+  DISALLOW_COPY_AND_ASSIGN(X11EventSourceGlib);
+};
+
+}  // namespace
+
+scoped_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() {
+  return scoped_ptr<PlatformEventSource>(
+      new X11EventSourceGlib(gfx::GetXDisplay()));
+}
+
+}  // namespace ui
diff --git a/ui/events/platform/x11/x11_event_source_libevent.cc b/ui/events/platform/x11/x11_event_source_libevent.cc
new file mode 100644
index 0000000..d92e12a
--- /dev/null
+++ b/ui/events/platform/x11/x11_event_source_libevent.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 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 "ui/events/platform/x11/x11_event_source.h"
+
+#include <X11/Xlib.h>
+
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_pump_libevent.h"
+
+namespace ui {
+
+namespace {
+
+class X11EventSourceLibevent : public X11EventSource,
+                               public base::MessagePumpLibevent::Watcher {
+ public:
+  explicit X11EventSourceLibevent(XDisplay* display)
+      : X11EventSource(display),
+        initialized_(false) {
+    AddEventWatcher();
+  }
+
+  virtual ~X11EventSourceLibevent() {
+  }
+
+ private:
+  void AddEventWatcher() {
+    if (initialized_)
+      return;
+    if (!base::MessageLoop::current())
+      return;
+
+    int fd = ConnectionNumber(display());
+    base::MessageLoopForUI::current()->WatchFileDescriptor(fd, true,
+        base::MessagePumpLibevent::WATCH_READ, &watcher_controller_, this);
+    initialized_ = true;
+  }
+
+  // PlatformEventSource:
+  virtual void OnDispatcherListChanged() OVERRIDE {
+    AddEventWatcher();
+  }
+
+  // base::MessagePumpLibevent::Watcher:
+  virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE {
+    DispatchXEvents();
+  }
+
+  virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE {
+    NOTREACHED();
+  }
+
+  base::MessagePumpLibevent::FileDescriptorWatcher watcher_controller_;
+  bool initialized_;
+
+  DISALLOW_COPY_AND_ASSIGN(X11EventSourceLibevent);
+};
+
+}  // namespace
+
+scoped_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() {
+  return scoped_ptr<PlatformEventSource>(
+      new X11EventSourceLibevent(gfx::GetXDisplay()));
+}
+
+}  // namespace ui
diff --git a/ui/events/platform/x11/x11_events_platform.gyp b/ui/events/platform/x11/x11_events_platform.gyp
new file mode 100644
index 0000000..0242d05
--- /dev/null
+++ b/ui/events/platform/x11/x11_events_platform.gyp
@@ -0,0 +1,45 @@
+# Copyright 2014 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.
+
+{
+  'variables': {
+    'chromium_code': 1,
+  },
+  'targets': [{
+    # GN version: //ui/events/platform/x11
+    'target_name': 'x11_events_platform',
+    'type': '<(component)',
+    'defines': [
+      'EVENTS_IMPLEMENTATION',
+    ],
+    'dependencies': [
+      '../../../../build/linux/system.gyp:x11',
+      '../../../gfx/x/gfx_x11.gyp:gfx_x11',
+      '../../events.gyp:events',
+      '../../events.gyp:events_base',
+      '../events_platform.gyp:events_platform',
+    ],
+    'sources': [
+      'x11_event_source.cc',
+      'x11_event_source.h',
+      'x11_event_source_glib.cc',
+      'x11_event_source_libevent.cc',
+    ],
+    'conditions': [
+      ['use_glib==1', {
+        'dependencies': [
+          '../../../../build/linux/system.gyp:glib',
+        ],
+        'sources!': [
+          'x11_event_source_libevent.cc',
+        ],
+      }, {
+        # use_glib == 0
+        'sources!': [
+          'x11_event_source_glib.cc',
+        ],
+      }],
+    ],
+  }],
+}
diff --git a/ui/events/test/cocoa_test_event_utils.h b/ui/events/test/cocoa_test_event_utils.h
new file mode 100644
index 0000000..76c6a32
--- /dev/null
+++ b/ui/events/test/cocoa_test_event_utils.h
@@ -0,0 +1,53 @@
+// Copyright 2014 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 UI_EVENTS_TEST_COCOA_TEST_EVENT_UTILS_H_
+#define UI_EVENTS_TEST_COCOA_TEST_EVENT_UTILS_H_
+
+#include <utility>
+
+#import <objc/objc-class.h>
+
+#include "base/basictypes.h"
+
+namespace cocoa_test_event_utils {
+
+// Create synthetic mouse events for testing. Currently these are very
+// basic, flesh out as needed.  Points are all in window coordinates;
+// where the window is not specified, coordinate system is undefined
+// (but will be repeated when the event is queried).
+NSEvent* MouseEventWithType(NSEventType type, NSUInteger modifiers);
+NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type,
+                           NSUInteger modifiers);
+NSEvent* LeftMouseDownAtPoint(NSPoint point);
+NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window);
+NSEvent* RightMouseDownAtPoint(NSPoint point);
+NSEvent* RightMouseDownAtPointInWindow(NSPoint point, NSWindow* window);
+
+// Return a mouse down and an up event with the given |clickCount| at
+// |view|'s midpoint.
+std::pair<NSEvent*, NSEvent*> MouseClickInView(NSView* view,
+                                               NSUInteger clickCount);
+
+// Returns a key event with the given character.
+NSEvent* KeyEventWithCharacter(unichar c);
+
+// Returns a key event with the given type and modifier flags.
+NSEvent* KeyEventWithType(NSEventType event_type, NSUInteger modifiers);
+
+// Returns a key event with the given key code, type, and modifier flags.
+NSEvent* KeyEventWithKeyCode(unsigned short key_code,
+                             unichar c,
+                             NSEventType event_type,
+                             NSUInteger modifiers);
+
+// Returns a mouse enter/exit event with the given type.
+NSEvent* EnterExitEventWithType(NSEventType event_type);
+
+// Return an "other" event with the given type.
+NSEvent* OtherEventWithType(NSEventType event_type);
+
+}  // namespace cocoa_test_event_utils
+
+#endif  // UI_EVENTS_TEST_COCOA_TEST_EVENT_UTILS_H_
diff --git a/ui/events/test/cocoa_test_event_utils.mm b/ui/events/test/cocoa_test_event_utils.mm
new file mode 100644
index 0000000..2694b84
--- /dev/null
+++ b/ui/events/test/cocoa_test_event_utils.mm
@@ -0,0 +1,135 @@
+// Copyright 2014 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.
+
+#import <Cocoa/Cocoa.h>
+
+#include "ui/events/test/cocoa_test_event_utils.h"
+
+namespace cocoa_test_event_utils {
+
+NSEvent* MouseEventAtPoint(NSPoint point, NSEventType type,
+                           NSUInteger modifiers) {
+  if (type == NSOtherMouseUp) {
+    // To synthesize middle clicks we need to create a CGEvent with the
+    // "center" button flags so that our resulting NSEvent will have the
+    // appropriate buttonNumber field. NSEvent provides no way to create a
+    // mouse event with a buttonNumber directly.
+    CGPoint location = { point.x, point.y };
+    CGEventRef cg_event = CGEventCreateMouseEvent(NULL, kCGEventOtherMouseUp,
+                                                  location,
+                                                  kCGMouseButtonCenter);
+    // Also specify the modifiers for the middle click case. This makes this
+    // test resilient to external modifiers being pressed.
+    CGEventSetFlags(cg_event, modifiers);
+    NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
+    CFRelease(cg_event);
+    return event;
+  }
+  return [NSEvent mouseEventWithType:type
+                            location:point
+                       modifierFlags:modifiers
+                           timestamp:0
+                        windowNumber:0
+                             context:nil
+                         eventNumber:0
+                          clickCount:1
+                            pressure:1.0];
+}
+
+NSEvent* MouseEventWithType(NSEventType type, NSUInteger modifiers) {
+  return MouseEventAtPoint(NSZeroPoint, type, modifiers);
+}
+
+static NSEvent* MouseEventAtPointInWindow(NSPoint point,
+                                          NSEventType type,
+                                          NSWindow* window,
+                                          NSUInteger clickCount) {
+  return [NSEvent mouseEventWithType:type
+                            location:point
+                       modifierFlags:0
+                           timestamp:0
+                        windowNumber:[window windowNumber]
+                             context:nil
+                         eventNumber:0
+                          clickCount:clickCount
+                            pressure:1.0];
+}
+
+NSEvent* RightMouseDownAtPointInWindow(NSPoint point, NSWindow* window) {
+  return MouseEventAtPointInWindow(point, NSRightMouseDown, window, 1);
+}
+
+NSEvent* RightMouseDownAtPoint(NSPoint point) {
+  return RightMouseDownAtPointInWindow(point, nil);
+}
+
+NSEvent* LeftMouseDownAtPointInWindow(NSPoint point, NSWindow* window) {
+  return MouseEventAtPointInWindow(point, NSLeftMouseDown, window, 1);
+}
+
+NSEvent* LeftMouseDownAtPoint(NSPoint point) {
+  return LeftMouseDownAtPointInWindow(point, nil);
+}
+
+std::pair<NSEvent*,NSEvent*> MouseClickInView(NSView* view,
+                                              NSUInteger clickCount) {
+  const NSRect bounds = [view convertRect:[view bounds] toView:nil];
+  const NSPoint mid_point = NSMakePoint(NSMidX(bounds), NSMidY(bounds));
+  NSEvent* down = MouseEventAtPointInWindow(mid_point, NSLeftMouseDown,
+                                            [view window], clickCount);
+  NSEvent* up = MouseEventAtPointInWindow(mid_point, NSLeftMouseUp,
+                                          [view window], clickCount);
+  return std::make_pair(down, up);
+}
+
+NSEvent* KeyEventWithCharacter(unichar c) {
+  return KeyEventWithKeyCode(0, c, NSKeyDown, 0);
+}
+
+NSEvent* KeyEventWithType(NSEventType event_type, NSUInteger modifiers) {
+  return KeyEventWithKeyCode(0x78, 'x', event_type, modifiers);
+}
+
+NSEvent* KeyEventWithKeyCode(unsigned short key_code,
+                             unichar c,
+                             NSEventType event_type,
+                             NSUInteger modifiers) {
+  NSString* chars = [NSString stringWithCharacters:&c length:1];
+  return [NSEvent keyEventWithType:event_type
+                          location:NSZeroPoint
+                     modifierFlags:modifiers
+                         timestamp:0
+                      windowNumber:0
+                           context:nil
+                        characters:chars
+       charactersIgnoringModifiers:chars
+                         isARepeat:NO
+                           keyCode:key_code];
+}
+
+NSEvent* EnterExitEventWithType(NSEventType event_type) {
+  return [NSEvent enterExitEventWithType:event_type
+                                location:NSZeroPoint
+                           modifierFlags:0
+                               timestamp:0
+                            windowNumber:0
+                                 context:nil
+                             eventNumber:0
+                          trackingNumber:0
+                                userData:NULL];
+}
+
+NSEvent* OtherEventWithType(NSEventType event_type) {
+  return [NSEvent otherEventWithType:event_type
+                            location:NSZeroPoint
+                       modifierFlags:0
+                           timestamp:0
+                        windowNumber:0
+                             context:nil
+                             subtype:0
+                               data1:0
+                               data2:0];
+}
+
+}  // namespace cocoa_test_event_utils
diff --git a/ui/events/test/event_generator.cc b/ui/events/test/event_generator.cc
new file mode 100644
index 0000000..ad9d597
--- /dev/null
+++ b/ui/events/test/event_generator.cc
@@ -0,0 +1,632 @@
+// Copyright 2014 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 "ui/events/test/event_generator.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/time/default_tick_clock.h"
+#include "ui/events/event.h"
+#include "ui/events/event_source.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/test/events_test_utils.h"
+#include "ui/gfx/vector2d_conversions.h"
+
+#if defined(USE_X11)
+#include <X11/Xlib.h>
+#include "ui/events/test/events_test_utils_x11.h"
+#endif
+
+#if defined(OS_WIN)
+#include "ui/events/keycodes/keyboard_code_conversion.h"
+#endif
+
+namespace ui {
+namespace test {
+namespace {
+
+void DummyCallback(EventType, const gfx::Vector2dF&) {
+}
+
+class TestKeyEvent : public ui::KeyEvent {
+ public:
+  TestKeyEvent(const base::NativeEvent& native_event, int flags)
+      : KeyEvent(native_event) {
+    set_flags(flags);
+  }
+};
+
+class TestTouchEvent : public ui::TouchEvent {
+ public:
+  TestTouchEvent(ui::EventType type,
+                 const gfx::Point& root_location,
+                 int touch_id,
+                 int flags,
+                 base::TimeDelta timestamp)
+      : TouchEvent(type, root_location, flags, touch_id, timestamp,
+                   1.0f, 1.0f, 0.0f, 0.0f) {
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestTouchEvent);
+};
+
+const int kAllButtonMask = ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON;
+
+}  // namespace
+
+EventGeneratorDelegate* EventGenerator::default_delegate = NULL;
+
+EventGenerator::EventGenerator(gfx::NativeWindow root_window)
+    : current_target_(NULL),
+      flags_(0),
+      grab_(false),
+      async_(false),
+      tick_clock_(new base::DefaultTickClock()) {
+  Init(root_window, NULL);
+}
+
+EventGenerator::EventGenerator(gfx::NativeWindow root_window,
+                               const gfx::Point& point)
+    : current_location_(point),
+      current_target_(NULL),
+      flags_(0),
+      grab_(false),
+      async_(false),
+      tick_clock_(new base::DefaultTickClock()) {
+  Init(root_window, NULL);
+}
+
+EventGenerator::EventGenerator(gfx::NativeWindow root_window,
+                               gfx::NativeWindow window)
+    : current_target_(NULL),
+      flags_(0),
+      grab_(false),
+      async_(false),
+      tick_clock_(new base::DefaultTickClock()) {
+  Init(root_window, window);
+}
+
+EventGenerator::EventGenerator(EventGeneratorDelegate* delegate)
+    : delegate_(delegate),
+      current_target_(NULL),
+      flags_(0),
+      grab_(false),
+      async_(false),
+      tick_clock_(new base::DefaultTickClock()) {
+  Init(NULL, NULL);
+}
+
+EventGenerator::~EventGenerator() {
+  for (std::list<ui::Event*>::iterator i = pending_events_.begin();
+      i != pending_events_.end(); ++i)
+    delete *i;
+  pending_events_.clear();
+  delegate()->SetContext(NULL, NULL, NULL);
+}
+
+void EventGenerator::PressLeftButton() {
+  PressButton(ui::EF_LEFT_MOUSE_BUTTON);
+}
+
+void EventGenerator::ReleaseLeftButton() {
+  ReleaseButton(ui::EF_LEFT_MOUSE_BUTTON);
+}
+
+void EventGenerator::ClickLeftButton() {
+  PressLeftButton();
+  ReleaseLeftButton();
+}
+
+void EventGenerator::DoubleClickLeftButton() {
+  flags_ |= ui::EF_IS_DOUBLE_CLICK;
+  PressLeftButton();
+  flags_ ^= ui::EF_IS_DOUBLE_CLICK;
+  ReleaseLeftButton();
+}
+
+void EventGenerator::PressRightButton() {
+  PressButton(ui::EF_RIGHT_MOUSE_BUTTON);
+}
+
+void EventGenerator::ReleaseRightButton() {
+  ReleaseButton(ui::EF_RIGHT_MOUSE_BUTTON);
+}
+
+void EventGenerator::MoveMouseWheel(int delta_x, int delta_y) {
+  gfx::Point location = GetLocationInCurrentRoot();
+  ui::MouseEvent mouseev(ui::ET_MOUSEWHEEL, location, location, flags_, 0);
+  ui::MouseWheelEvent wheelev(mouseev, delta_x, delta_y);
+  Dispatch(&wheelev);
+}
+
+void EventGenerator::SendMouseExit() {
+  gfx::Point exit_location(current_location_);
+  delegate()->ConvertPointToTarget(current_target_, &exit_location);
+  ui::MouseEvent mouseev(ui::ET_MOUSE_EXITED, exit_location, exit_location,
+                         flags_, 0);
+  Dispatch(&mouseev);
+}
+
+void EventGenerator::MoveMouseToInHost(const gfx::Point& point_in_host) {
+  const ui::EventType event_type = (flags_ & ui::EF_LEFT_MOUSE_BUTTON) ?
+      ui::ET_MOUSE_DRAGGED : ui::ET_MOUSE_MOVED;
+  ui::MouseEvent mouseev(event_type, point_in_host, point_in_host, flags_, 0);
+  Dispatch(&mouseev);
+
+  current_location_ = point_in_host;
+  delegate()->ConvertPointFromHost(current_target_, &current_location_);
+}
+
+void EventGenerator::MoveMouseTo(const gfx::Point& point_in_screen,
+                                 int count) {
+  DCHECK_GT(count, 0);
+  const ui::EventType event_type = (flags_ & ui::EF_LEFT_MOUSE_BUTTON) ?
+      ui::ET_MOUSE_DRAGGED : ui::ET_MOUSE_MOVED;
+
+  gfx::Vector2dF diff(point_in_screen - current_location_);
+  for (float i = 1; i <= count; i++) {
+    gfx::Vector2dF step(diff);
+    step.Scale(i / count);
+    gfx::Point move_point = current_location_ + gfx::ToRoundedVector2d(step);
+    if (!grab_)
+      UpdateCurrentDispatcher(move_point);
+    delegate()->ConvertPointToTarget(current_target_, &move_point);
+    ui::MouseEvent mouseev(event_type, move_point, move_point, flags_, 0);
+    Dispatch(&mouseev);
+  }
+  current_location_ = point_in_screen;
+}
+
+void EventGenerator::MoveMouseRelativeTo(const EventTarget* window,
+                                         const gfx::Point& point_in_parent) {
+  gfx::Point point(point_in_parent);
+  delegate()->ConvertPointFromTarget(window, &point);
+  MoveMouseTo(point);
+}
+
+void EventGenerator::DragMouseTo(const gfx::Point& point) {
+  PressLeftButton();
+  MoveMouseTo(point);
+  ReleaseLeftButton();
+}
+
+void EventGenerator::MoveMouseToCenterOf(EventTarget* window) {
+  MoveMouseTo(CenterOfWindow(window));
+}
+
+void EventGenerator::PressTouch() {
+  PressTouchId(0);
+}
+
+void EventGenerator::PressTouchId(int touch_id) {
+  TestTouchEvent touchev(
+      ui::ET_TOUCH_PRESSED, GetLocationInCurrentRoot(), touch_id, flags_,
+      Now());
+  Dispatch(&touchev);
+}
+
+void EventGenerator::MoveTouch(const gfx::Point& point) {
+  MoveTouchId(point, 0);
+}
+
+void EventGenerator::MoveTouchId(const gfx::Point& point, int touch_id) {
+  current_location_ = point;
+  TestTouchEvent touchev(
+      ui::ET_TOUCH_MOVED, GetLocationInCurrentRoot(), touch_id, flags_,
+      Now());
+  Dispatch(&touchev);
+
+  if (!grab_)
+    UpdateCurrentDispatcher(point);
+}
+
+void EventGenerator::ReleaseTouch() {
+  ReleaseTouchId(0);
+}
+
+void EventGenerator::ReleaseTouchId(int touch_id) {
+  TestTouchEvent touchev(
+      ui::ET_TOUCH_RELEASED, GetLocationInCurrentRoot(), touch_id, flags_,
+      Now());
+  Dispatch(&touchev);
+}
+
+void EventGenerator::PressMoveAndReleaseTouchTo(const gfx::Point& point) {
+  PressTouch();
+  MoveTouch(point);
+  ReleaseTouch();
+}
+
+void EventGenerator::PressMoveAndReleaseTouchToCenterOf(EventTarget* window) {
+  PressMoveAndReleaseTouchTo(CenterOfWindow(window));
+}
+
+void EventGenerator::GestureEdgeSwipe() {
+  ui::GestureEvent gesture(
+      0, 0, 0, Now(), ui::GestureEventDetails(ui::ET_GESTURE_WIN8_EDGE_SWIPE));
+  Dispatch(&gesture);
+}
+
+void EventGenerator::GestureTapAt(const gfx::Point& location) {
+  const int kTouchId = 2;
+  ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+                       location,
+                       kTouchId,
+                       Now());
+  Dispatch(&press);
+
+  ui::TouchEvent release(
+      ui::ET_TOUCH_RELEASED, location, kTouchId,
+      press.time_stamp() + base::TimeDelta::FromMilliseconds(50));
+  Dispatch(&release);
+}
+
+void EventGenerator::GestureTapDownAndUp(const gfx::Point& location) {
+  const int kTouchId = 3;
+  ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+                       location,
+                       kTouchId,
+                       Now());
+  Dispatch(&press);
+
+  ui::TouchEvent release(
+      ui::ET_TOUCH_RELEASED, location, kTouchId,
+      press.time_stamp() + base::TimeDelta::FromMilliseconds(1000));
+  Dispatch(&release);
+}
+
+void EventGenerator::GestureScrollSequence(const gfx::Point& start,
+                                           const gfx::Point& end,
+                                           const base::TimeDelta& step_delay,
+                                           int steps) {
+  GestureScrollSequenceWithCallback(start, end, step_delay, steps,
+                                    base::Bind(&DummyCallback));
+}
+
+void EventGenerator::GestureScrollSequenceWithCallback(
+    const gfx::Point& start,
+    const gfx::Point& end,
+    const base::TimeDelta& step_delay,
+    int steps,
+    const ScrollStepCallback& callback) {
+  const int kTouchId = 5;
+  base::TimeDelta timestamp = Now();
+  ui::TouchEvent press(ui::ET_TOUCH_PRESSED, start, kTouchId, timestamp);
+  Dispatch(&press);
+
+  callback.Run(ui::ET_GESTURE_SCROLL_BEGIN, gfx::Vector2dF());
+
+  int dx = (end.x() - start.x()) / steps;
+  int dy = (end.y() - start.y()) / steps;
+  gfx::Point location = start;
+  for (int i = 0; i < steps; ++i) {
+    location.Offset(dx, dy);
+    timestamp += step_delay;
+    ui::TouchEvent move(ui::ET_TOUCH_MOVED, location, kTouchId, timestamp);
+    Dispatch(&move);
+    callback.Run(ui::ET_GESTURE_SCROLL_UPDATE, gfx::Vector2dF(dx, dy));
+  }
+
+  ui::TouchEvent release(ui::ET_TOUCH_RELEASED, end, kTouchId, timestamp);
+  Dispatch(&release);
+
+  callback.Run(ui::ET_GESTURE_SCROLL_END, gfx::Vector2dF());
+}
+
+void EventGenerator::GestureMultiFingerScroll(int count,
+                                              const gfx::Point start[],
+                                              int event_separation_time_ms,
+                                              int steps,
+                                              int move_x,
+                                              int move_y) {
+  const int kMaxTouchPoints = 10;
+  int delays[kMaxTouchPoints] = { 0 };
+  GestureMultiFingerScrollWithDelays(
+      count, start, delays, event_separation_time_ms, steps, move_x, move_y);
+}
+
+void EventGenerator::GestureMultiFingerScrollWithDelays(
+    int count,
+    const gfx::Point start[],
+    const int delay_adding_finger_ms[],
+    int event_separation_time_ms,
+    int steps,
+    int move_x,
+    int move_y) {
+  const int kMaxTouchPoints = 10;
+  gfx::Point points[kMaxTouchPoints];
+  CHECK_LE(count, kMaxTouchPoints);
+  CHECK_GT(steps, 0);
+
+  int delta_x = move_x / steps;
+  int delta_y = move_y / steps;
+
+  for (int i = 0; i < count; ++i) {
+    points[i] = start[i];
+  }
+
+  base::TimeDelta press_time_first = Now();
+  base::TimeDelta press_time[kMaxTouchPoints];
+  bool pressed[kMaxTouchPoints];
+  for (int i = 0; i < count; ++i) {
+    pressed[i] = false;
+    press_time[i] = press_time_first +
+        base::TimeDelta::FromMilliseconds(delay_adding_finger_ms[i]);
+  }
+
+  int last_id = 0;
+  for (int step = 0; step < steps; ++step) {
+    base::TimeDelta move_time = press_time_first +
+        base::TimeDelta::FromMilliseconds(event_separation_time_ms * step);
+
+    while (last_id < count &&
+           !pressed[last_id] &&
+           move_time >= press_time[last_id]) {
+      ui::TouchEvent press(ui::ET_TOUCH_PRESSED,
+                           points[last_id],
+                           last_id,
+                           press_time[last_id]);
+      Dispatch(&press);
+      pressed[last_id] = true;
+      last_id++;
+    }
+
+    for (int i = 0; i < count; ++i) {
+      points[i].Offset(delta_x, delta_y);
+      if (i >= last_id)
+        continue;
+      ui::TouchEvent move(ui::ET_TOUCH_MOVED, points[i], i, move_time);
+      Dispatch(&move);
+    }
+  }
+
+  base::TimeDelta release_time = press_time_first +
+      base::TimeDelta::FromMilliseconds(event_separation_time_ms * steps);
+  for (int i = 0; i < last_id; ++i) {
+    ui::TouchEvent release(
+        ui::ET_TOUCH_RELEASED, points[i], i, release_time);
+    Dispatch(&release);
+  }
+}
+
+void EventGenerator::ScrollSequence(const gfx::Point& start,
+                                    const base::TimeDelta& step_delay,
+                                    float x_offset,
+                                    float y_offset,
+                                    int steps,
+                                    int num_fingers) {
+  base::TimeDelta timestamp = Now();
+  ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL,
+                               start,
+                               timestamp,
+                               0,
+                               0, 0,
+                               0, 0,
+                               num_fingers);
+  Dispatch(&fling_cancel);
+
+  float dx = x_offset / steps;
+  float dy = y_offset / steps;
+  for (int i = 0; i < steps; ++i) {
+    timestamp += step_delay;
+    ui::ScrollEvent move(ui::ET_SCROLL,
+                         start,
+                         timestamp,
+                         0,
+                         dx, dy,
+                         dx, dy,
+                         num_fingers);
+    Dispatch(&move);
+  }
+
+  ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START,
+                              start,
+                              timestamp,
+                              0,
+                              x_offset, y_offset,
+                              x_offset, y_offset,
+                              num_fingers);
+  Dispatch(&fling_start);
+}
+
+void EventGenerator::ScrollSequence(const gfx::Point& start,
+                                    const base::TimeDelta& step_delay,
+                                    const std::vector<gfx::Point>& offsets,
+                                    int num_fingers) {
+  size_t steps = offsets.size();
+  base::TimeDelta timestamp = Now();
+  ui::ScrollEvent fling_cancel(ui::ET_SCROLL_FLING_CANCEL,
+                               start,
+                               timestamp,
+                               0,
+                               0, 0,
+                               0, 0,
+                               num_fingers);
+  Dispatch(&fling_cancel);
+
+  for (size_t i = 0; i < steps; ++i) {
+    timestamp += step_delay;
+    ui::ScrollEvent scroll(ui::ET_SCROLL,
+                           start,
+                           timestamp,
+                           0,
+                           offsets[i].x(), offsets[i].y(),
+                           offsets[i].x(), offsets[i].y(),
+                           num_fingers);
+    Dispatch(&scroll);
+  }
+
+  ui::ScrollEvent fling_start(ui::ET_SCROLL_FLING_START,
+                              start,
+                              timestamp,
+                              0,
+                              offsets[steps - 1].x(), offsets[steps - 1].y(),
+                              offsets[steps - 1].x(), offsets[steps - 1].y(),
+                              num_fingers);
+  Dispatch(&fling_start);
+}
+
+void EventGenerator::PressKey(ui::KeyboardCode key_code, int flags) {
+  DispatchKeyEvent(true, key_code, flags);
+}
+
+void EventGenerator::ReleaseKey(ui::KeyboardCode key_code, int flags) {
+  DispatchKeyEvent(false, key_code, flags);
+}
+
+void EventGenerator::Dispatch(ui::Event* event) {
+  DoDispatchEvent(event, async_);
+}
+
+void EventGenerator::SetTickClock(scoped_ptr<base::TickClock> tick_clock) {
+  tick_clock_ = tick_clock.Pass();
+}
+
+base::TimeDelta EventGenerator::Now() {
+  // This is the same as what EventTimeForNow() does, but here we do it
+  // with a tick clock that can be replaced with a simulated clock for tests.
+  return base::TimeDelta::FromInternalValue(
+      tick_clock_->NowTicks().ToInternalValue());
+}
+
+void EventGenerator::Init(gfx::NativeWindow root_window,
+                          gfx::NativeWindow window_context) {
+  delegate()->SetContext(this, root_window, window_context);
+  if (window_context)
+    current_location_ = delegate()->CenterOfWindow(window_context);
+  current_target_ = delegate()->GetTargetAt(current_location_);
+}
+
+void EventGenerator::DispatchKeyEvent(bool is_press,
+                                      ui::KeyboardCode key_code,
+                                      int flags) {
+#if defined(OS_WIN)
+  UINT key_press = WM_KEYDOWN;
+  uint16 character = ui::GetCharacterFromKeyCode(key_code, flags);
+  if (is_press && character) {
+    MSG native_event = { NULL, WM_KEYDOWN, key_code, 0 };
+    TestKeyEvent keyev(native_event, flags);
+    Dispatch(&keyev);
+    // On Windows, WM_KEYDOWN event is followed by WM_CHAR with a character
+    // if the key event cooresponds to a real character.
+    key_press = WM_CHAR;
+    key_code = static_cast<ui::KeyboardCode>(character);
+  }
+  MSG native_event =
+      { NULL, (is_press ? key_press : WM_KEYUP), key_code, 0 };
+  TestKeyEvent keyev(native_event, flags);
+#elif defined(USE_X11)
+  ui::ScopedXI2Event xevent;
+  xevent.InitKeyEvent(is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED,
+                      key_code,
+                      flags);
+  ui::KeyEvent keyev(xevent);
+#else
+  ui::EventType type = is_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED;
+  ui::KeyEvent keyev(type, key_code, flags);
+#endif  // OS_WIN
+  Dispatch(&keyev);
+}
+
+void EventGenerator::UpdateCurrentDispatcher(const gfx::Point& point) {
+  current_target_ = delegate()->GetTargetAt(point);
+}
+
+void EventGenerator::PressButton(int flag) {
+  if (!(flags_ & flag)) {
+    flags_ |= flag;
+    grab_ = (flags_ & kAllButtonMask) != 0;
+    gfx::Point location = GetLocationInCurrentRoot();
+    ui::MouseEvent mouseev(ui::ET_MOUSE_PRESSED, location, location, flags_,
+                           flag);
+    Dispatch(&mouseev);
+  }
+}
+
+void EventGenerator::ReleaseButton(int flag) {
+  if (flags_ & flag) {
+    gfx::Point location = GetLocationInCurrentRoot();
+    ui::MouseEvent mouseev(ui::ET_MOUSE_RELEASED, location,
+                           location, flags_, flag);
+    Dispatch(&mouseev);
+    flags_ ^= flag;
+  }
+  grab_ = (flags_ & kAllButtonMask) != 0;
+}
+
+gfx::Point EventGenerator::GetLocationInCurrentRoot() const {
+  gfx::Point p(current_location_);
+  delegate()->ConvertPointToTarget(current_target_, &p);
+  return p;
+}
+
+gfx::Point EventGenerator::CenterOfWindow(const EventTarget* window) const {
+  return delegate()->CenterOfTarget(window);
+}
+
+void EventGenerator::DoDispatchEvent(ui::Event* event, bool async) {
+  if (async) {
+    ui::Event* pending_event;
+    if (event->IsKeyEvent()) {
+      pending_event = new ui::KeyEvent(*static_cast<ui::KeyEvent*>(event));
+    } else if (event->IsMouseEvent()) {
+      pending_event = new ui::MouseEvent(*static_cast<ui::MouseEvent*>(event));
+    } else if (event->IsTouchEvent()) {
+      pending_event = new ui::TouchEvent(*static_cast<ui::TouchEvent*>(event));
+    } else if (event->IsScrollEvent()) {
+      pending_event =
+          new ui::ScrollEvent(*static_cast<ui::ScrollEvent*>(event));
+    } else {
+      NOTREACHED() << "Invalid event type";
+      return;
+    }
+    if (pending_events_.empty()) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE,
+          base::Bind(&EventGenerator::DispatchNextPendingEvent,
+                     base::Unretained(this)));
+    }
+    pending_events_.push_back(pending_event);
+  } else {
+    ui::EventSource* event_source = delegate()->GetEventSource(current_target_);
+    ui::EventSourceTestApi event_source_test(event_source);
+    ui::EventDispatchDetails details =
+        event_source_test.SendEventToProcessor(event);
+    CHECK(!details.dispatcher_destroyed);
+  }
+}
+
+void EventGenerator::DispatchNextPendingEvent() {
+  DCHECK(!pending_events_.empty());
+  ui::Event* event = pending_events_.front();
+  DoDispatchEvent(event, false);
+  pending_events_.pop_front();
+  delete event;
+  if (!pending_events_.empty()) {
+    base::ThreadTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE,
+        base::Bind(&EventGenerator::DispatchNextPendingEvent,
+                   base::Unretained(this)));
+  }
+}
+
+const EventGeneratorDelegate* EventGenerator::delegate() const {
+  if (delegate_)
+    return delegate_.get();
+
+  DCHECK(default_delegate);
+  return default_delegate;
+}
+
+EventGeneratorDelegate* EventGenerator::delegate() {
+  return const_cast<EventGeneratorDelegate*>(
+      const_cast<const EventGenerator*>(this)->delegate());
+}
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/test/event_generator.h b/ui/events/test/event_generator.h
new file mode 100644
index 0000000..bc243b9
--- /dev/null
+++ b/ui/events/test/event_generator.h
@@ -0,0 +1,385 @@
+// Copyright 2014 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 UI_EVENTS_TEST_EVENT_GENERATOR_H_
+#define UI_EVENTS_TEST_EVENT_GENERATOR_H_
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/point.h"
+
+namespace base {
+class TickClock;
+}
+
+namespace ui {
+class Event;
+class EventProcessor;
+class EventSource;
+class EventTarget;
+class KeyEvent;
+class MouseEvent;
+class ScrollEvent;
+class TouchEvent;
+
+namespace test {
+
+typedef base::Callback<void(EventType, const gfx::Vector2dF&)>
+        ScrollStepCallback;
+
+class EventGenerator;
+
+// A delegate interface for EventGenerator to abstract platform-specific event
+// targeting and coordinate conversion.
+class EventGeneratorDelegate {
+ public:
+  virtual ~EventGeneratorDelegate() {}
+
+  // Set the context of the delegate, whilst it is being used by an active
+  // EventGenerator.
+  virtual void SetContext(EventGenerator* owner,
+                          gfx::NativeWindow root_window,
+                          gfx::NativeWindow window) {}
+
+  // The ui::EventTarget at the given |location|.
+  virtual EventTarget* GetTargetAt(const gfx::Point& location) = 0;
+
+  // The ui::EventSource for the given |target|.
+  virtual EventSource* GetEventSource(EventTarget* target) = 0;
+
+  // Helper functions to determine the center point of |target| or |window|.
+  virtual gfx::Point CenterOfTarget(const EventTarget* target) const = 0;
+  virtual gfx::Point CenterOfWindow(gfx::NativeWindow window) const = 0;
+
+  // Convert a point between API's coordinates and |target|'s coordinates.
+  virtual void ConvertPointFromTarget(const EventTarget* target,
+                                      gfx::Point* point) const = 0;
+  virtual void ConvertPointToTarget(const EventTarget* target,
+                                    gfx::Point* point) const = 0;
+
+  // Convert a point from the coordinate system in the host that contains
+  // |hosted_target| into the root window's coordinate system.
+  virtual void ConvertPointFromHost(const EventTarget* hosted_target,
+                                    gfx::Point* point) const = 0;
+};
+
+// ui::test::EventGenerator is a tool that generates and dispatches events.
+// Unlike |ui_controls| package in ui/base/test, this does not use platform
+// native message loops. Instead, it sends events to the event dispatcher
+// synchronously.
+//
+// This class is not suited for the following cases:
+//
+// 1) If your test depends on native events (ui::Event::native_event()).
+//   This return is empty/NULL event with EventGenerator.
+// 2) If your test involves nested message loop, such as
+//    menu or drag & drop. Because this class directly
+//    post an event to WindowEventDispatcher, this event will not be
+//    handled in the nested message loop.
+// 3) Similarly, |base::MessagePumpObserver| will not be invoked.
+// 4) Any other code that requires native message loops, such as
+//    tests for WindowTreeHostWin/WindowTreeHostX11.
+//
+// If one of these applies to your test, please use |ui_controls|
+// package instead.
+//
+// Note: The coordinates of the points in API is determined by the
+// EventGeneratorDelegate.
+class EventGenerator {
+ public:
+  // Creates an EventGenerator with the mouse/touch location (0,0),
+  // which uses the |root_window|'s coordinates and the default delegate for
+  // this platform.
+  explicit EventGenerator(gfx::NativeWindow root_window);
+
+  // Create an EventGenerator with EventGeneratorDelegate,
+  // which uses the coordinates conversions and targeting provided by
+  // |delegate|.
+  explicit EventGenerator(EventGeneratorDelegate* delegate);
+
+  // Creates an EventGenerator with the mouse/touch location
+  // at |initial_location|, which uses the |root_window|'s coordinates.
+  EventGenerator(gfx::NativeWindow root_window,
+                 const gfx::Point& initial_location);
+
+  // Creates an EventGenerator with the mouse/touch location centered over
+  // |window|. This is currently the only constructor that works on Mac, since
+  // a specific window is required (and there is no root window).
+  EventGenerator(gfx::NativeWindow root_window, gfx::NativeWindow window);
+
+  virtual ~EventGenerator();
+
+  // Explicitly sets the location used by mouse/touch events. This is set by the
+  // various methods that take a location but can be manipulated directly,
+  // typically for touch.
+  void set_current_location(const gfx::Point& location) {
+    current_location_ = location;
+  }
+  const gfx::Point& current_location() const { return current_location_; }
+
+  void set_async(bool async) { async_ = async; }
+  bool async() const { return async_; }
+
+  // Resets the event flags bitmask.
+  void set_flags(int flags) { flags_ = flags; }
+  int flags() const { return flags_; }
+
+  // Generates a left button press event.
+  void PressLeftButton();
+
+  // Generates a left button release event.
+  void ReleaseLeftButton();
+
+  // Generates events to click (press, release) left button.
+  void ClickLeftButton();
+
+  // Generates a double click event using the left button.
+  void DoubleClickLeftButton();
+
+  // Generates a right button press event.
+  void PressRightButton();
+
+  // Generates a right button release event.
+  void ReleaseRightButton();
+
+  // Moves the mouse wheel by |delta_x|, |delta_y|.
+  void MoveMouseWheel(int delta_x, int delta_y);
+
+  // Generates a mouse exit.
+  void SendMouseExit();
+
+  // Generates events to move mouse to be the given |point| in the
+  // |current_root_window_|'s host window coordinates.
+  void MoveMouseToInHost(const gfx::Point& point_in_host);
+  void MoveMouseToInHost(int x, int y) {
+    MoveMouseToInHost(gfx::Point(x, y));
+  }
+
+  // Generates events to move mouse to be the given |point| in screen
+  // coordinates.
+  void MoveMouseTo(const gfx::Point& point_in_screen, int count);
+  void MoveMouseTo(const gfx::Point& point_in_screen) {
+    MoveMouseTo(point_in_screen, 1);
+  }
+  void MoveMouseTo(int x, int y) {
+    MoveMouseTo(gfx::Point(x, y));
+  }
+
+  // Generates events to move mouse to be the given |point| in |window|'s
+  // coordinates.
+  void MoveMouseRelativeTo(const EventTarget* window, const gfx::Point& point);
+  void MoveMouseRelativeTo(const EventTarget* window, int x, int y) {
+    MoveMouseRelativeTo(window, gfx::Point(x, y));
+  }
+
+  void MoveMouseBy(int x, int y) {
+    MoveMouseTo(current_location_ + gfx::Vector2d(x, y));
+  }
+
+  // Generates events to drag mouse to given |point|.
+  void DragMouseTo(const gfx::Point& point);
+
+  void DragMouseTo(int x, int y) {
+    DragMouseTo(gfx::Point(x, y));
+  }
+
+  void DragMouseBy(int dx, int dy) {
+    DragMouseTo(current_location_ + gfx::Vector2d(dx, dy));
+  }
+
+  // Generates events to move the mouse to the center of the window.
+  void MoveMouseToCenterOf(EventTarget* window);
+
+  // Generates a touch press event.
+  void PressTouch();
+
+  // Generates a touch press event with |touch_id|.
+  void PressTouchId(int touch_id);
+
+  // Generates a ET_TOUCH_MOVED event to |point|.
+  void MoveTouch(const gfx::Point& point);
+
+  // Generates a ET_TOUCH_MOVED event to |point| with |touch_id|.
+  void MoveTouchId(const gfx::Point& point, int touch_id);
+
+  // Generates a touch release event.
+  void ReleaseTouch();
+
+  // Generates a touch release event with |touch_id|.
+  void ReleaseTouchId(int touch_id);
+
+  // Generates press, move and release event to move touch
+  // to be the given |point|.
+  void PressMoveAndReleaseTouchTo(const gfx::Point& point);
+
+  void PressMoveAndReleaseTouchTo(int x, int y) {
+    PressMoveAndReleaseTouchTo(gfx::Point(x, y));
+  }
+
+  void PressMoveAndReleaseTouchBy(int x, int y) {
+    PressMoveAndReleaseTouchTo(current_location_ + gfx::Vector2d(x, y));
+  }
+
+  // Generates press, move and release events to move touch
+  // to the center of the window.
+  void PressMoveAndReleaseTouchToCenterOf(EventTarget* window);
+
+  // Generates and dispatches a Win8 edge-swipe event (swipe up from bottom or
+  // swipe down from top).  Note that it is not possible to distinguish between
+  // the two edges with this event.
+  void GestureEdgeSwipe();
+
+  // Generates and dispatches touch-events required to generate a TAP gesture.
+  // Note that this can generate a number of other gesture events at the same
+  // time (e.g. GESTURE_BEGIN, TAP_DOWN, END).
+  void GestureTapAt(const gfx::Point& point);
+
+  // Generates press and release touch-events to generate a TAP_DOWN event, but
+  // without generating any scroll or tap events. This can also generate a few
+  // other gesture events (e.g. GESTURE_BEGIN, END).
+  void GestureTapDownAndUp(const gfx::Point& point);
+
+  // Generates press, move, release touch-events to generate a sequence of
+  // scroll events. |duration| and |steps| affect the velocity of the scroll,
+  // and depending on these values, this may also generate FLING scroll
+  // gestures. If velocity/fling is irrelevant for the test, then any non-zero
+  // values for these should be sufficient.
+  void GestureScrollSequence(const gfx::Point& start,
+                             const gfx::Point& end,
+                             const base::TimeDelta& duration,
+                             int steps);
+
+  // The same as GestureScrollSequence(), with the exception that |callback| is
+  // called at each step of the scroll sequence. |callback| is called at the
+  // start of the sequence with ET_GESTURE_SCROLL_BEGIN, followed by one or more
+  // ET_GESTURE_SCROLL_UPDATE and ends with an ET_GESTURE_SCROLL_END.
+  void GestureScrollSequenceWithCallback(const gfx::Point& start,
+                                         const gfx::Point& end,
+                                         const base::TimeDelta& duration,
+                                         int steps,
+                                         const ScrollStepCallback& callback);
+
+  // Generates press, move, release touch-events to generate a sequence of
+  // multi-finger scroll events. |count| specifies the number of touch-points
+  // that should generate the scroll events. |start| are the starting positions
+  // of all the touch points. |steps| and |event_separation_time_ms| are
+  // relevant when testing velocity/fling/swipe, otherwise these can be any
+  // non-zero value. |delta_x| and |delta_y| are the amount that each finger
+  // should be moved. Internally calls GestureMultiFingerScrollWithDelays
+  // with zeros as |delay_adding_finger_ms| forcing all touch down events to be
+  // immediate.
+  void GestureMultiFingerScroll(int count,
+                                const gfx::Point start[],
+                                int event_separation_time_ms,
+                                int steps,
+                                int move_x,
+                                int move_y);
+
+  // Generates press, move, release touch-events to generate a sequence of
+  // multi-finger scroll events. |count| specifies the number of touch-points
+  // that should generate the scroll events. |start| are the starting positions
+  // of all the touch points. |delay_adding_finger_ms| are delays in ms from the
+  // starting time till touching down of each finger. |delay_adding_finger_ms|
+  // is useful when testing complex gestures that start with 1 or 2 fingers and
+  // add fingers with a delay. |steps| and |event_separation_time_ms| are
+  // relevant when testing velocity/fling/swipe, otherwise these can be any
+  // non-zero value. |delta_x| and |delta_y| are the amount that each finger
+  // should be moved.
+  void GestureMultiFingerScrollWithDelays(int count,
+                                          const gfx::Point start[],
+                                          const int delay_adding_finger_ms[],
+                                          int event_separation_time_ms,
+                                          int steps,
+                                          int move_x,
+                                          int move_y);
+
+  // Generates scroll sequences of a FlingCancel, Scrolls, FlingStart, with
+  // constant deltas to |x_offset| and |y_offset| in |steps|.
+  void ScrollSequence(const gfx::Point& start,
+                      const base::TimeDelta& step_delay,
+                      float x_offset,
+                      float y_offset,
+                      int steps,
+                      int num_fingers);
+
+  // Generates scroll sequences of a FlingCancel, Scrolls, FlingStart, sending
+  // scrolls of each of the values in |offsets|.
+  void ScrollSequence(const gfx::Point& start,
+                      const base::TimeDelta& step_delay,
+                      const std::vector<gfx::Point>& offsets,
+                      int num_fingers);
+
+  // Generates a key press event. On platforms except Windows and X11, a key
+  // event without native_event() is generated. Note that ui::EF_ flags should
+  // be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
+  // TODO(yusukes): Support native_event() on all platforms.
+  void PressKey(KeyboardCode key_code, int flags);
+
+  // Generates a key release event. On platforms except Windows and X11, a key
+  // event without native_event() is generated. Note that ui::EF_ flags should
+  // be passed as |flags|, not the native ones like 'ShiftMask' in <X11/X.h>.
+  // TODO(yusukes): Support native_event() on all platforms.
+  void ReleaseKey(KeyboardCode key_code, int flags);
+
+  // Dispatch the event to the WindowEventDispatcher.
+  void Dispatch(Event* event);
+
+  void set_current_target(EventTarget* target) {
+    current_target_ = target;
+  }
+
+  // Specify an alternative tick clock to be used for simulating time in tests.
+  void SetTickClock(scoped_ptr<base::TickClock> tick_clock);
+
+  // Get the current time from the tick clock.
+  base::TimeDelta Now();
+
+  // Default delegate set by a platform-specific GeneratorDelegate singleton.
+  static EventGeneratorDelegate* default_delegate;
+
+ private:
+  // Set up the test context using the delegate.
+  void Init(gfx::NativeWindow root_window, gfx::NativeWindow window_context);
+
+  // Dispatch a key event to the WindowEventDispatcher.
+  void DispatchKeyEvent(bool is_press, KeyboardCode key_code, int flags);
+
+  void UpdateCurrentDispatcher(const gfx::Point& point);
+  void PressButton(int flag);
+  void ReleaseButton(int flag);
+
+  gfx::Point GetLocationInCurrentRoot() const;
+  gfx::Point CenterOfWindow(const EventTarget* window) const;
+
+  void DispatchNextPendingEvent();
+  void DoDispatchEvent(Event* event, bool async);
+
+  const EventGeneratorDelegate* delegate() const;
+  EventGeneratorDelegate* delegate();
+
+  scoped_ptr<EventGeneratorDelegate> delegate_;
+  gfx::Point current_location_;
+  EventTarget* current_target_;
+  int flags_;
+  bool grab_;
+  std::list<Event*> pending_events_;
+  // Set to true to cause events to be posted asynchronously.
+  bool async_;
+  scoped_ptr<base::TickClock> tick_clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventGenerator);
+};
+
+}  // namespace test
+}  // namespace ui
+
+#endif  // UI_EVENTS_TEST_EVENT_GENERATOR_H_
diff --git a/ui/events/test/events_test_utils.cc b/ui/events/test/events_test_utils.cc
new file mode 100644
index 0000000..8c3f1b6
--- /dev/null
+++ b/ui/events/test/events_test_utils.cc
@@ -0,0 +1,36 @@
+// Copyright 2013 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 "ui/events/test/events_test_utils.h"
+
+#include "ui/events/event_source.h"
+
+namespace ui {
+
+EventTestApi::EventTestApi(Event* event) : event_(event) {}
+EventTestApi::~EventTestApi() {}
+
+LocatedEventTestApi::LocatedEventTestApi(LocatedEvent* event)
+    : EventTestApi(event),
+      located_event_(event) {}
+LocatedEventTestApi::~LocatedEventTestApi() {}
+
+KeyEventTestApi::KeyEventTestApi(KeyEvent* event)
+    : EventTestApi(event),
+      key_event_(event) {}
+KeyEventTestApi::~KeyEventTestApi() {}
+
+EventTargetTestApi::EventTargetTestApi(EventTarget* target)
+    : target_(target) {}
+
+EventSourceTestApi::EventSourceTestApi(EventSource* event_source)
+    : event_source_(event_source) {
+  DCHECK(event_source);
+}
+
+EventDispatchDetails EventSourceTestApi::SendEventToProcessor(Event* event) {
+  return event_source_->SendEventToProcessor(event);
+}
+
+}  // namespace ui
diff --git a/ui/events/test/events_test_utils.h b/ui/events/test/events_test_utils.h
new file mode 100644
index 0000000..3ed6fa3
--- /dev/null
+++ b/ui/events/test/events_test_utils.h
@@ -0,0 +1,103 @@
+// Copyright 2013 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 UI_EVENTS_TEST_EVENTS_TEST_UTILS_H_
+#define UI_EVENTS_TEST_EVENTS_TEST_UTILS_H_
+
+#include "ui/events/event.h"
+#include "ui/events/event_dispatcher.h"
+#include "ui/events/event_target.h"
+
+namespace ui {
+
+class EventSource;
+
+class EventTestApi {
+ public:
+  explicit EventTestApi(Event* event);
+  virtual ~EventTestApi();
+
+  void set_time_stamp(base::TimeDelta time_stamp) {
+    event_->time_stamp_ = time_stamp;
+  }
+
+  void set_source_device_id(int source_device_id) {
+    event_->source_device_id_ = source_device_id;
+  }
+
+ private:
+  EventTestApi();
+
+  Event* event_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventTestApi);
+};
+
+class LocatedEventTestApi : public EventTestApi {
+ public:
+  explicit LocatedEventTestApi(LocatedEvent* located_event);
+  virtual ~LocatedEventTestApi();
+
+  void set_location(const gfx::Point& location) {
+    located_event_->location_ = location;
+  }
+
+ private:
+  LocatedEventTestApi();
+
+  LocatedEvent* located_event_;
+
+  DISALLOW_COPY_AND_ASSIGN(LocatedEventTestApi);
+};
+
+class KeyEventTestApi : public EventTestApi {
+ public:
+  explicit KeyEventTestApi(KeyEvent* key_event);
+  virtual ~KeyEventTestApi();
+
+  void set_is_char(bool is_char) {
+    key_event_->set_is_char(is_char);
+  }
+
+ private:
+  KeyEventTestApi();
+
+  KeyEvent* key_event_;
+
+  DISALLOW_COPY_AND_ASSIGN(KeyEventTestApi);
+};
+
+class EventTargetTestApi {
+ public:
+  explicit EventTargetTestApi(EventTarget* target);
+
+  const EventHandlerList& pre_target_handlers() {
+    return target_->pre_target_list_;
+  }
+
+ private:
+  EventTargetTestApi();
+
+  EventTarget* target_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventTargetTestApi);
+};
+
+class EventSourceTestApi {
+ public:
+  explicit EventSourceTestApi(EventSource* event_source);
+
+  EventDispatchDetails SendEventToProcessor(Event* event) WARN_UNUSED_RESULT;
+
+ private:
+  EventSourceTestApi();
+
+  EventSource* event_source_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventSourceTestApi);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_TEST_EVENTS_TEST_UTILS_H_
diff --git a/ui/events/test/events_test_utils_x11.cc b/ui/events/test/events_test_utils_x11.cc
new file mode 100644
index 0000000..28558a2
--- /dev/null
+++ b/ui/events/test/events_test_utils_x11.cc
@@ -0,0 +1,304 @@
+// Copyright 2013 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 "ui/events/test/events_test_utils_x11.h"
+
+#include <X11/extensions/XI2.h>
+#include <X11/keysym.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+
+#include "base/logging.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include "ui/events/x/touch_factory_x11.h"
+
+namespace {
+
+// Converts ui::EventType to state for X*Events.
+unsigned int XEventState(int flags) {
+  return
+      ((flags & ui::EF_SHIFT_DOWN) ? ShiftMask : 0) |
+      ((flags & ui::EF_CONTROL_DOWN) ? ControlMask : 0) |
+      ((flags & ui::EF_ALT_DOWN) ? Mod1Mask : 0) |
+      ((flags & ui::EF_CAPS_LOCK_DOWN) ? LockMask : 0) |
+      ((flags & ui::EF_ALTGR_DOWN) ? Mod5Mask : 0) |
+      ((flags & ui::EF_COMMAND_DOWN) ? Mod4Mask : 0) |
+      ((flags & ui::EF_MOD3_DOWN) ? Mod3Mask : 0) |
+      ((flags & ui::EF_NUMPAD_KEY) ? Mod2Mask : 0) |
+      ((flags & ui::EF_LEFT_MOUSE_BUTTON) ? Button1Mask: 0) |
+      ((flags & ui::EF_MIDDLE_MOUSE_BUTTON) ? Button2Mask: 0) |
+      ((flags & ui::EF_RIGHT_MOUSE_BUTTON) ? Button3Mask: 0);
+}
+
+// Converts EventType to XKeyEvent type.
+int XKeyEventType(ui::EventType type) {
+  switch (type) {
+    case ui::ET_KEY_PRESSED:
+      return KeyPress;
+    case ui::ET_KEY_RELEASED:
+      return KeyRelease;
+    default:
+      return 0;
+  }
+}
+
+// Converts EventType to XI2 event type.
+int XIKeyEventType(ui::EventType type) {
+  switch (type) {
+    case ui::ET_KEY_PRESSED:
+      return XI_KeyPress;
+    case ui::ET_KEY_RELEASED:
+      return XI_KeyRelease;
+    default:
+      return 0;
+  }
+}
+
+int XIButtonEventType(ui::EventType type) {
+  switch (type) {
+    case ui::ET_MOUSEWHEEL:
+    case ui::ET_MOUSE_PRESSED:
+      // The button release X events for mouse wheels are dropped by Aura.
+      return XI_ButtonPress;
+    case ui::ET_MOUSE_RELEASED:
+      return XI_ButtonRelease;
+    default:
+      NOTREACHED();
+      return 0;
+  }
+}
+
+// Converts Aura event type and flag to X button event.
+unsigned int XButtonEventButton(ui::EventType type,
+                                int flags) {
+  // Aura events don't keep track of mouse wheel button, so just return
+  // the first mouse wheel button.
+  if (type == ui::ET_MOUSEWHEEL)
+    return Button4;
+
+  if (flags & ui::EF_LEFT_MOUSE_BUTTON)
+    return Button1;
+  if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
+    return Button2;
+  if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
+    return Button3;
+
+  return 0;
+}
+
+void InitValuatorsForXIDeviceEvent(XIDeviceEvent* xiev) {
+  int valuator_count = ui::DeviceDataManagerX11::DT_LAST_ENTRY;
+  xiev->valuators.mask_len = (valuator_count / 8) + 1;
+  xiev->valuators.mask = new unsigned char[xiev->valuators.mask_len];
+  memset(xiev->valuators.mask, 0, xiev->valuators.mask_len);
+  xiev->valuators.values = new double[valuator_count];
+}
+
+XEvent* CreateXInput2Event(int deviceid,
+                           int evtype,
+                           int tracking_id,
+                           const gfx::Point& location) {
+  XEvent* event = new XEvent;
+  memset(event, 0, sizeof(*event));
+  event->type = GenericEvent;
+  event->xcookie.data = new XIDeviceEvent;
+  XIDeviceEvent* xiev =
+      static_cast<XIDeviceEvent*>(event->xcookie.data);
+  memset(xiev, 0, sizeof(XIDeviceEvent));
+  xiev->deviceid = deviceid;
+  xiev->sourceid = deviceid;
+  xiev->evtype = evtype;
+  xiev->detail = tracking_id;
+  xiev->event_x = location.x();
+  xiev->event_y = location.y();
+  xiev->event = DefaultRootWindow(gfx::GetXDisplay());
+  if (evtype == XI_ButtonPress || evtype == XI_ButtonRelease) {
+    xiev->buttons.mask_len = 8;
+    xiev->buttons.mask = new unsigned char[xiev->buttons.mask_len];
+    memset(xiev->buttons.mask, 0, xiev->buttons.mask_len);
+  }
+  return event;
+}
+
+}  // namespace
+
+namespace ui {
+
+// XInput2 events contain additional data that need to be explicitly freed (see
+// |CreateXInput2Event()|.
+void XEventDeleter::operator()(XEvent* event) {
+  if (event->type == GenericEvent) {
+    XIDeviceEvent* xiev =
+        static_cast<XIDeviceEvent*>(event->xcookie.data);
+    if (xiev) {
+      delete[] xiev->valuators.mask;
+      delete[] xiev->valuators.values;
+      delete[] xiev->buttons.mask;
+      delete xiev;
+    }
+  }
+  delete event;
+}
+
+ScopedXI2Event::ScopedXI2Event() {}
+ScopedXI2Event::~ScopedXI2Event() {}
+
+void ScopedXI2Event::InitKeyEvent(EventType type,
+                                  KeyboardCode key_code,
+                                  int flags) {
+  XDisplay* display = gfx::GetXDisplay();
+  event_.reset(new XEvent);
+  memset(event_.get(), 0, sizeof(XEvent));
+  event_->type = XKeyEventType(type);
+  CHECK_NE(0, event_->type);
+  event_->xkey.serial = 0;
+  event_->xkey.send_event = 0;
+  event_->xkey.display = display;
+  event_->xkey.time = 0;
+  event_->xkey.window = 0;
+  event_->xkey.root = 0;
+  event_->xkey.subwindow = 0;
+  event_->xkey.x = 0;
+  event_->xkey.y = 0;
+  event_->xkey.x_root = 0;
+  event_->xkey.y_root = 0;
+  event_->xkey.state = XEventState(flags);
+  event_->xkey.keycode = XKeyCodeForWindowsKeyCode(key_code, flags, display);
+  event_->xkey.same_screen = 1;
+}
+
+void ScopedXI2Event::InitGenericKeyEvent(int deviceid,
+                                         int sourceid,
+                                         EventType type,
+                                         KeyboardCode key_code,
+                                         int flags) {
+  event_.reset(
+      CreateXInput2Event(deviceid, XIKeyEventType(type), 0, gfx::Point()));
+  XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+  CHECK_NE(0, xievent->evtype);
+  XDisplay* display = gfx::GetXDisplay();
+  event_->xgeneric.display = display;
+  xievent->display = display;
+  xievent->mods.effective = XEventState(flags);
+  xievent->detail = XKeyCodeForWindowsKeyCode(key_code, flags, display);
+  xievent->sourceid = sourceid;
+}
+
+void ScopedXI2Event::InitGenericButtonEvent(int deviceid,
+                                            EventType type,
+                                            const gfx::Point& location,
+                                            int flags) {
+  event_.reset(CreateXInput2Event(deviceid,
+                                  XIButtonEventType(type), 0, gfx::Point()));
+  XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+  xievent->mods.effective = XEventState(flags);
+  xievent->detail = XButtonEventButton(type, flags);
+  xievent->event_x = location.x();
+  xievent->event_y = location.y();
+  XISetMask(xievent->buttons.mask, xievent->detail);
+  // Setup an empty valuator list for generic button events.
+  SetUpValuators(std::vector<Valuator>());
+}
+
+void ScopedXI2Event::InitGenericMouseWheelEvent(int deviceid,
+                                                int wheel_delta,
+                                                int flags) {
+  InitGenericButtonEvent(deviceid, ui::ET_MOUSEWHEEL, gfx::Point(), flags);
+  XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+  xievent->detail = wheel_delta > 0 ? Button4 : Button5;
+}
+
+void ScopedXI2Event::InitScrollEvent(int deviceid,
+                                     int x_offset,
+                                     int y_offset,
+                                     int x_offset_ordinal,
+                                     int y_offset_ordinal,
+                                     int finger_count) {
+  event_.reset(CreateXInput2Event(deviceid, XI_Motion, 0, gfx::Point()));
+
+  Valuator valuators[] = {
+    Valuator(DeviceDataManagerX11::DT_CMT_SCROLL_X, x_offset),
+    Valuator(DeviceDataManagerX11::DT_CMT_SCROLL_Y, y_offset),
+    Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_X, x_offset_ordinal),
+    Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_Y, y_offset_ordinal),
+    Valuator(DeviceDataManagerX11::DT_CMT_FINGER_COUNT, finger_count)
+  };
+  SetUpValuators(
+      std::vector<Valuator>(valuators, valuators + arraysize(valuators)));
+}
+
+void ScopedXI2Event::InitFlingScrollEvent(int deviceid,
+                                          int x_velocity,
+                                          int y_velocity,
+                                          int x_velocity_ordinal,
+                                          int y_velocity_ordinal,
+                                          bool is_cancel) {
+  event_.reset(CreateXInput2Event(deviceid, XI_Motion, deviceid, gfx::Point()));
+
+  Valuator valuators[] = {
+    Valuator(DeviceDataManagerX11::DT_CMT_FLING_STATE, is_cancel ? 1 : 0),
+    Valuator(DeviceDataManagerX11::DT_CMT_FLING_Y, y_velocity),
+    Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_Y, y_velocity_ordinal),
+    Valuator(DeviceDataManagerX11::DT_CMT_FLING_X, x_velocity),
+    Valuator(DeviceDataManagerX11::DT_CMT_ORDINAL_X, x_velocity_ordinal)
+  };
+
+  SetUpValuators(
+      std::vector<Valuator>(valuators, valuators + arraysize(valuators)));
+}
+
+void ScopedXI2Event::InitTouchEvent(int deviceid,
+                                    int evtype,
+                                    int tracking_id,
+                                    const gfx::Point& location,
+                                    const std::vector<Valuator>& valuators) {
+  event_.reset(CreateXInput2Event(deviceid, evtype, tracking_id, location));
+
+  // If a timestamp was specified, setup the event.
+  for (size_t i = 0; i < valuators.size(); ++i) {
+    if (valuators[i].data_type ==
+        DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP) {
+      SetUpValuators(valuators);
+      return;
+    }
+  }
+
+  // No timestamp was specified. Use |ui::EventTimeForNow()|.
+  std::vector<Valuator> valuators_with_time = valuators;
+  valuators_with_time.push_back(
+      Valuator(DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP,
+               (ui::EventTimeForNow()).InMicroseconds()));
+  SetUpValuators(valuators_with_time);
+}
+
+void ScopedXI2Event::SetUpValuators(const std::vector<Valuator>& valuators) {
+  CHECK(event_.get());
+  CHECK_EQ(GenericEvent, event_->type);
+  XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(event_->xcookie.data);
+  InitValuatorsForXIDeviceEvent(xiev);
+  ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+  for (size_t i = 0; i < valuators.size(); ++i) {
+    manager->SetValuatorDataForTest(xiev, valuators[i].data_type,
+                                    valuators[i].value);
+  }
+}
+
+void SetUpTouchPadForTest(unsigned int deviceid) {
+  std::vector<unsigned int> device_list;
+  device_list.push_back(deviceid);
+
+  TouchFactory::GetInstance()->SetPointerDeviceForTest(device_list);
+  ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+  manager->SetDeviceListForTest(std::vector<unsigned int>(), device_list);
+}
+
+void SetUpTouchDevicesForTest(const std::vector<unsigned int>& devices) {
+  TouchFactory::GetInstance()->SetTouchDeviceForTest(devices);
+  ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+  manager->SetDeviceListForTest(devices, std::vector<unsigned int>());
+}
+
+}  // namespace ui
diff --git a/ui/events/test/events_test_utils_x11.h b/ui/events/test/events_test_utils_x11.h
new file mode 100644
index 0000000..cce83ce
--- /dev/null
+++ b/ui/events/test/events_test_utils_x11.h
@@ -0,0 +1,100 @@
+// Copyright 2013 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 UI_EVENTS_TEST_EVENTS_TEST_UTILS_X11_H_
+#define UI_EVENTS_TEST_EVENTS_TEST_UTILS_X11_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/x/x11_types.h"
+
+typedef union _XEvent XEvent;
+
+namespace ui {
+
+struct Valuator {
+  Valuator(DeviceDataManagerX11::DataType type, double v)
+      : data_type(type), value(v) {}
+
+  DeviceDataManagerX11::DataType data_type;
+  double value;
+};
+
+struct XEventDeleter {
+  void operator()(XEvent* event);
+};
+
+class ScopedXI2Event {
+ public:
+  ScopedXI2Event();
+  ~ScopedXI2Event();
+
+  operator XEvent*() { return event_.get(); }
+
+  // Initializes a XEvent with for the appropriate type with the specified data.
+  // Note that ui::EF_ flags should be passed as |flags|, not the native ones in
+  // <X11/X.h>.
+  void InitKeyEvent(EventType type,
+                    KeyboardCode key_code,
+                    int flags);
+
+  // Initializes an Xinput2 key event.
+  // |deviceid| is the master, and |sourceid| is the slave device.
+  void InitGenericKeyEvent(int deviceid,
+                           int sourceid,
+                           EventType type,
+                           KeyboardCode key_code,
+                           int flags);
+
+  void InitGenericButtonEvent(int deviceid,
+                              EventType type,
+                              const gfx::Point& location,
+                              int flags);
+
+  void InitGenericMouseWheelEvent(int deviceid,
+                                  int wheel_delta,
+                                  int flags);
+
+  void InitScrollEvent(int deviceid,
+                       int x_offset,
+                       int y_offset,
+                       int x_offset_ordinal,
+                       int y_offset_ordinal,
+                       int finger_count);
+
+  void InitFlingScrollEvent(int deviceid,
+                            int x_velocity,
+                            int y_velocity,
+                            int x_velocity_ordinal,
+                            int y_velocity_ordinal,
+                            bool is_cancel);
+
+  void InitTouchEvent(int deviceid,
+                      int evtype,
+                      int tracking_id,
+                      const gfx::Point& location,
+                      const std::vector<Valuator>& valuators);
+
+ private:
+  void Cleanup();
+
+  void SetUpValuators(const std::vector<Valuator>& valuators);
+
+  scoped_ptr<XEvent, XEventDeleter> event_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedXI2Event);
+};
+
+// Initializes a test touchpad device for scroll events.
+void SetUpTouchPadForTest(unsigned int deviceid);
+
+// Initializes a list of touchscreen devices for touch events.
+void SetUpTouchDevicesForTest(const std::vector<unsigned int>& devices);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_TEST_EVENTS_TEST_UTILS_X11_H_
diff --git a/ui/events/test/mock_motion_event.cc b/ui/events/test/mock_motion_event.cc
new file mode 100644
index 0000000..24d8159
--- /dev/null
+++ b/ui/events/test/mock_motion_event.cc
@@ -0,0 +1,177 @@
+// Copyright 2014 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 "ui/events/test/mock_motion_event.h"
+
+#include "base/logging.h"
+
+using base::TimeTicks;
+
+namespace ui {
+namespace test {
+namespace {
+
+PointerProperties CreatePointer() {
+  PointerProperties pointer;
+  pointer.touch_major = MockMotionEvent::TOUCH_MAJOR;
+  return pointer;
+}
+
+PointerProperties CreatePointer(float x, float y, int id) {
+  PointerProperties pointer(x, y);
+  pointer.touch_major = MockMotionEvent::TOUCH_MAJOR;
+  pointer.id = id;
+  return pointer;
+}
+
+
+}  // namespace
+
+MockMotionEvent::MockMotionEvent()
+    : MotionEventGeneric(ACTION_CANCEL, base::TimeTicks(), CreatePointer()) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action)
+    : MotionEventGeneric(action, base::TimeTicks(), CreatePointer()) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+                                 TimeTicks time,
+                                 float x0,
+                                 float y0)
+    : MotionEventGeneric(action, time, CreatePointer(x0, y0, 0)) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+                                 TimeTicks time,
+                                 float x0,
+                                 float y0,
+                                 float x1,
+                                 float y1)
+    : MotionEventGeneric(action, time, CreatePointer(x0, y0, 0)) {
+  PushPointer(x1, y1);
+  if (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN)
+    set_action_index(1);
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+                                 TimeTicks time,
+                                 float x0,
+                                 float y0,
+                                 float x1,
+                                 float y1,
+                                 float x2,
+                                 float y2)
+    : MotionEventGeneric(action, time, CreatePointer(x0, y0, 0)) {
+  PushPointer(x1, y1);
+  PushPointer(x2, y2);
+  if (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN)
+    set_action_index(2);
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+                                 base::TimeTicks time,
+                                 const std::vector<gfx::PointF>& positions) {
+  set_action(action);
+  set_event_time(time);
+  if (action == ACTION_POINTER_UP || action == ACTION_POINTER_DOWN)
+    set_action_index(static_cast<int>(positions.size()) - 1);
+  for (size_t i = 0; i < positions.size(); ++i)
+    PushPointer(positions[i].x(), positions[i].y());
+}
+
+MockMotionEvent::MockMotionEvent(const MockMotionEvent& other)
+    : MotionEventGeneric(other) {
+}
+
+MockMotionEvent::~MockMotionEvent() {}
+
+scoped_ptr<MotionEvent> MockMotionEvent::Clone() const {
+  return scoped_ptr<MotionEvent>(new MockMotionEvent(*this));
+}
+
+scoped_ptr<MotionEvent> MockMotionEvent::Cancel() const {
+  scoped_ptr<MockMotionEvent> event(new MockMotionEvent(*this));
+  event->set_action(MotionEvent::ACTION_CANCEL);
+  return event.PassAs<MotionEvent>();
+}
+
+void MockMotionEvent::PressPoint(float x, float y) {
+  ResolvePointers();
+  PushPointer(x, y);
+  if (GetPointerCount() > 1) {
+    set_action_index(static_cast<int>(GetPointerCount()) - 1);
+    set_action(ACTION_POINTER_DOWN);
+  } else {
+    set_action(ACTION_DOWN);
+  }
+}
+
+void MockMotionEvent::MovePoint(size_t index, float x, float y) {
+  ResolvePointers();
+  DCHECK_LT(index, GetPointerCount());
+  PointerProperties& p = pointer(index);
+  float dx = x - p.x;
+  float dy = x - p.y;
+  p.x = x;
+  p.y = y;
+  p.raw_x += dx;
+  p.raw_y += dy;
+  set_action(ACTION_MOVE);
+}
+
+void MockMotionEvent::ReleasePoint() {
+  ResolvePointers();
+  DCHECK_GT(GetPointerCount(), 0U);
+  if (GetPointerCount() > 1) {
+    set_action_index(static_cast<int>(GetPointerCount()) - 1);
+    set_action(ACTION_POINTER_UP);
+  } else {
+    set_action(ACTION_UP);
+  }
+}
+
+void MockMotionEvent::CancelPoint() {
+  ResolvePointers();
+  DCHECK_GT(GetPointerCount(), 0U);
+  set_action(ACTION_CANCEL);
+}
+
+void MockMotionEvent::SetTouchMajor(float new_touch_major) {
+  for (size_t i = 0; i < GetPointerCount(); ++i)
+    pointer(i).touch_major = new_touch_major;
+}
+
+void MockMotionEvent::SetRawOffset(float raw_offset_x, float raw_offset_y) {
+  for (size_t i = 0; i < GetPointerCount(); ++i) {
+    pointer(i).raw_x = pointer(i).x + raw_offset_x;
+    pointer(i).raw_y = pointer(i).y + raw_offset_y;
+  }
+}
+
+void MockMotionEvent::SetToolType(size_t pointer_index, ToolType tool_type) {
+  DCHECK_LT(pointer_index, GetPointerCount());
+  pointer(pointer_index).tool_type = tool_type;
+}
+
+void MockMotionEvent::PushPointer(float x, float y) {
+  MotionEventGeneric::PushPointer(
+      CreatePointer(x, y, static_cast<int>(GetPointerCount())));
+}
+
+void MockMotionEvent::ResolvePointers() {
+  set_action_index(-1);
+  switch (GetAction()) {
+    case ACTION_UP:
+    case ACTION_POINTER_UP:
+    case ACTION_CANCEL:
+      PopPointer();
+      return;
+    default:
+      break;
+  }
+}
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/test/mock_motion_event.h b/ui/events/test/mock_motion_event.h
new file mode 100644
index 0000000..9ae9a71
--- /dev/null
+++ b/ui/events/test/mock_motion_event.h
@@ -0,0 +1,61 @@
+// Copyright 2014 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 <vector>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/motion_event_generic.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace ui {
+namespace test {
+
+struct MockMotionEvent : public MotionEventGeneric {
+  enum { TOUCH_MAJOR = 10 };
+
+  MockMotionEvent();
+  explicit MockMotionEvent(Action action);
+  MockMotionEvent(Action action, base::TimeTicks time, float x, float y);
+  MockMotionEvent(Action action,
+                  base::TimeTicks time,
+                  float x0,
+                  float y0,
+                  float x1,
+                  float y1);
+  MockMotionEvent(Action action,
+                  base::TimeTicks time,
+                  float x0,
+                  float y0,
+                  float x1,
+                  float y1,
+                  float x2,
+                  float y2);
+  MockMotionEvent(Action action,
+                  base::TimeTicks time,
+                  const std::vector<gfx::PointF>& positions);
+  MockMotionEvent(const MockMotionEvent& other);
+
+  virtual ~MockMotionEvent();
+
+  // MotionEvent methods.
+  virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE;
+  virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE;
+
+  // Utility methods.
+  void PressPoint(float x, float y);
+  void MovePoint(size_t index, float x, float y);
+  void ReleasePoint();
+  void CancelPoint();
+  void SetTouchMajor(float new_touch_major);
+  void SetRawOffset(float raw_offset_x, float raw_offset_y);
+  void SetToolType(size_t index, ToolType tool_type);
+
+ private:
+  void PushPointer(float x, float y);
+  void ResolvePointers();
+};
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/test/platform_event_waiter.cc b/ui/events/test/platform_event_waiter.cc
new file mode 100644
index 0000000..e024da5
--- /dev/null
+++ b/ui/events/test/platform_event_waiter.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 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 "ui/events/test/platform_event_waiter.h"
+
+#include "base/message_loop/message_loop.h"
+#include "ui/events/platform/platform_event_source.h"
+
+namespace ui {
+
+PlatformEventWaiter::PlatformEventWaiter(
+    const base::Closure& success_callback,
+    const PlatformEventMatcher& event_matcher)
+    : success_callback_(success_callback),
+      event_matcher_(event_matcher) {
+  PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
+}
+
+PlatformEventWaiter::~PlatformEventWaiter() {
+  PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
+}
+
+void PlatformEventWaiter::WillProcessEvent(const PlatformEvent& event) {
+  if (event_matcher_.Run(event)) {
+    base::MessageLoop::current()->PostTask(FROM_HERE, success_callback_);
+    delete this;
+  }
+}
+
+void PlatformEventWaiter::DidProcessEvent(const PlatformEvent& event) {
+}
+
+// static
+PlatformEventWaiter* PlatformEventWaiter::Create(
+    const base::Closure& success_callback,
+    const PlatformEventMatcher& event_matcher) {
+  return new PlatformEventWaiter(success_callback, event_matcher);
+}
+
+}  // namespace ui
diff --git a/ui/events/test/platform_event_waiter.h b/ui/events/test/platform_event_waiter.h
new file mode 100644
index 0000000..259662c
--- /dev/null
+++ b/ui/events/test/platform_event_waiter.h
@@ -0,0 +1,37 @@
+// Copyright 2014 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 UI_EVENTS_TEST_PLATFORM_EVENT_WAITER_H_
+#define UI_EVENTS_TEST_PLATFORM_EVENT_WAITER_H_
+
+#include "base/callback.h"
+#include "ui/events/platform/platform_event_observer.h"
+
+namespace ui {
+
+class PlatformEventWaiter : public PlatformEventObserver {
+ public:
+  typedef base::Callback<bool(const PlatformEvent&)> PlatformEventMatcher;
+
+  static PlatformEventWaiter* Create(const base::Closure& success_callback,
+                                     const PlatformEventMatcher& event_matcher);
+
+ private:
+  PlatformEventWaiter(const base::Closure& success_callback,
+                      const PlatformEventMatcher& event_matcher);
+  virtual ~PlatformEventWaiter();
+
+  // PlatformEventObserver:
+  virtual void WillProcessEvent(const PlatformEvent& event) OVERRIDE;
+  virtual void DidProcessEvent(const PlatformEvent& event) OVERRIDE;
+
+  base::Closure success_callback_;
+  PlatformEventMatcher event_matcher_;
+
+  DISALLOW_COPY_AND_ASSIGN(PlatformEventWaiter);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_TEST_PLATFORM_EVENT_WAITER_H_
diff --git a/ui/events/test/test_event_handler.cc b/ui/events/test/test_event_handler.cc
new file mode 100644
index 0000000..f97aad5
--- /dev/null
+++ b/ui/events/test/test_event_handler.cc
@@ -0,0 +1,64 @@
+// Copyright (c) 2012 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 "ui/events/test/test_event_handler.h"
+
+#include "ui/events/event.h"
+
+namespace ui {
+namespace test {
+
+TestEventHandler::TestEventHandler()
+    : num_key_events_(0),
+      num_mouse_events_(0),
+      num_scroll_events_(0),
+      num_touch_events_(0),
+      num_gesture_events_(0),
+      recorder_(NULL),
+      handler_name_("unknown") {
+}
+
+TestEventHandler::~TestEventHandler() {}
+
+void TestEventHandler::Reset() {
+  num_key_events_ = 0;
+  num_mouse_events_ = 0;
+  num_scroll_events_ = 0;
+  num_touch_events_ = 0;
+  num_gesture_events_ = 0;
+}
+
+void TestEventHandler::OnKeyEvent(KeyEvent* event) {
+  if (recorder_)
+    recorder_->push_back(handler_name_);
+  num_key_events_++;
+  event->SetHandled();
+}
+
+void TestEventHandler::OnMouseEvent(MouseEvent* event) {
+  if (recorder_)
+    recorder_->push_back(handler_name_);
+  num_mouse_events_++;
+}
+
+void TestEventHandler::OnScrollEvent(ScrollEvent* event) {
+  if (recorder_)
+    recorder_->push_back(handler_name_);
+  num_scroll_events_++;
+}
+
+void TestEventHandler::OnTouchEvent(TouchEvent* event) {
+  if (recorder_)
+    recorder_->push_back(handler_name_);
+  num_touch_events_++;
+}
+
+void TestEventHandler::OnGestureEvent(GestureEvent* event) {
+  if (recorder_)
+    recorder_->push_back(handler_name_);
+  num_gesture_events_++;
+}
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/test/test_event_handler.h b/ui/events/test/test_event_handler.h
new file mode 100644
index 0000000..8937b25
--- /dev/null
+++ b/ui/events/test/test_event_handler.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 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 UI_EVENTS_TEST_TEST_EVENT_HANDLER_H_
+#define UI_EVENTS_TEST_TEST_EVENT_HANDLER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ui/events/event_handler.h"
+
+typedef std::vector<std::string> HandlerSequenceRecorder;
+
+namespace ui {
+namespace test {
+
+// A simple EventHandler that keeps track of the number of key events that it's
+// seen.
+class TestEventHandler : public EventHandler {
+ public:
+  TestEventHandler();
+  virtual ~TestEventHandler();
+
+  int num_key_events() const { return num_key_events_; }
+  int num_mouse_events() const { return num_mouse_events_; }
+  int num_scroll_events() const { return num_scroll_events_; }
+  int num_touch_events() const { return num_touch_events_; }
+  int num_gesture_events() const { return num_gesture_events_; }
+
+  void Reset();
+
+  void set_recorder(HandlerSequenceRecorder* recorder) {
+    recorder_ = recorder;
+  }
+  void set_handler_name(const std::string& handler_name) {
+    handler_name_ = handler_name;
+  }
+
+  // EventHandler overrides:
+  virtual void OnKeyEvent(KeyEvent* event) OVERRIDE;
+  virtual void OnMouseEvent(MouseEvent* event) OVERRIDE;
+  virtual void OnScrollEvent(ScrollEvent* event) OVERRIDE;
+  virtual void OnTouchEvent(TouchEvent* event) OVERRIDE;
+  virtual void OnGestureEvent(GestureEvent* event) OVERRIDE;
+
+ private:
+  // How many events have been received of each type?
+  int num_key_events_;
+  int num_mouse_events_;
+  int num_scroll_events_;
+  int num_touch_events_;
+  int num_gesture_events_;
+
+  HandlerSequenceRecorder* recorder_;
+  std::string handler_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
+};
+
+}  // namespace test
+}  // namespace ui
+
+#endif // UI_EVENTS_TEST_TEST_EVENT_HANDLER_H_
diff --git a/ui/events/test/test_event_processor.cc b/ui/events/test/test_event_processor.cc
new file mode 100644
index 0000000..3be8016
--- /dev/null
+++ b/ui/events/test/test_event_processor.cc
@@ -0,0 +1,53 @@
+// Copyright 2013 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 "ui/events/test/test_event_processor.h"
+
+#include "ui/events/event_target.h"
+
+namespace ui {
+namespace test {
+
+TestEventProcessor::TestEventProcessor()
+    : should_processing_occur_(true),
+      num_times_processing_started_(0),
+      num_times_processing_finished_(0) {
+}
+
+TestEventProcessor::~TestEventProcessor() {}
+
+void TestEventProcessor::SetRoot(scoped_ptr<EventTarget> root) {
+  root_ = root.Pass();
+}
+
+void TestEventProcessor::Reset() {
+  should_processing_occur_ = true;
+  num_times_processing_started_ = 0;
+  num_times_processing_finished_ = 0;
+}
+
+bool TestEventProcessor::CanDispatchToTarget(EventTarget* target) {
+  return true;
+}
+
+EventTarget* TestEventProcessor::GetRootTarget() {
+  return root_.get();
+}
+
+EventDispatchDetails TestEventProcessor::OnEventFromSource(Event* event) {
+  return EventProcessor::OnEventFromSource(event);
+}
+
+void TestEventProcessor::OnEventProcessingStarted(Event* event) {
+  num_times_processing_started_++;
+  if (!should_processing_occur_)
+    event->SetHandled();
+}
+
+void TestEventProcessor::OnEventProcessingFinished(Event* event) {
+  num_times_processing_finished_++;
+}
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/test/test_event_processor.h b/ui/events/test/test_event_processor.h
new file mode 100644
index 0000000..5b9a99b
--- /dev/null
+++ b/ui/events/test/test_event_processor.h
@@ -0,0 +1,60 @@
+// Copyright 2013 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 UI_EVENTS_TEST_TEST_EVENT_PROCESSOR_H_
+#define UI_EVENTS_TEST_TEST_EVENT_PROCESSOR_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event_processor.h"
+
+namespace ui {
+namespace test {
+
+class TestEventProcessor : public EventProcessor {
+ public:
+  TestEventProcessor();
+  virtual ~TestEventProcessor();
+
+  int num_times_processing_started() const {
+    return num_times_processing_started_;
+  }
+
+  int num_times_processing_finished() const {
+    return num_times_processing_finished_;
+  }
+
+  void set_should_processing_occur(bool occur) {
+    should_processing_occur_ = occur;
+  }
+
+  void SetRoot(scoped_ptr<EventTarget> root);
+  void Reset();
+
+  // EventProcessor:
+  virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE;
+  virtual EventTarget* GetRootTarget() OVERRIDE;
+  virtual EventDispatchDetails OnEventFromSource(Event* event) OVERRIDE;
+  virtual void OnEventProcessingStarted(Event* event) OVERRIDE;
+  virtual void OnEventProcessingFinished(Event* event) OVERRIDE;
+
+ private:
+  scoped_ptr<EventTarget> root_;
+
+  // Used in our override of OnEventProcessingStarted(). If this value is
+  // false, mark incoming events as handled.
+  bool should_processing_occur_;
+
+  // Counts the number of times OnEventProcessingStarted() has been called.
+  int num_times_processing_started_;
+
+  // Counts the number of times OnEventProcessingFinished() has been called.
+  int num_times_processing_finished_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestEventProcessor);
+};
+
+}  // namespace test
+}  // namespace ui
+
+#endif  // UI_EVENTS_TEST_TEST_EVENT_PROCESSOR_H_
diff --git a/ui/events/test/test_event_target.cc b/ui/events/test/test_event_target.cc
new file mode 100644
index 0000000..1274dd5
--- /dev/null
+++ b/ui/events/test/test_event_target.cc
@@ -0,0 +1,94 @@
+// Copyright 2013 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 "ui/events/test/test_event_target.h"
+
+#include "ui/events/event.h"
+#include "ui/events/event_target_iterator.h"
+#include "ui/events/event_targeter.h"
+
+namespace ui {
+namespace test {
+
+TestEventTarget::TestEventTarget()
+    : parent_(NULL),
+      mark_events_as_handled_(false),
+      recorder_(NULL),
+      target_name_("unknown") {}
+TestEventTarget::~TestEventTarget() {}
+
+void TestEventTarget::AddChild(scoped_ptr<TestEventTarget> child) {
+  TestEventTarget* child_r = child.get();
+  if (child->parent()) {
+    AddChild(child->parent()->RemoveChild(child.release()));
+  } else {
+    children_.push_back(child.release());
+  }
+  child_r->set_parent(this);
+}
+
+scoped_ptr<TestEventTarget> TestEventTarget::RemoveChild(TestEventTarget *c) {
+  ScopedVector<TestEventTarget>::iterator iter = std::find(children_.begin(),
+                                                           children_.end(),
+                                                           c);
+  if (iter != children_.end()) {
+    children_.weak_erase(iter);
+    c->set_parent(NULL);
+    return scoped_ptr<TestEventTarget>(c);
+  }
+  return scoped_ptr<TestEventTarget>();
+}
+
+void TestEventTarget::SetEventTargeter(scoped_ptr<EventTargeter> targeter) {
+  targeter_ = targeter.Pass();
+}
+
+bool TestEventTarget::DidReceiveEvent(ui::EventType type) const {
+  return received_.count(type) > 0;
+}
+
+void TestEventTarget::ResetReceivedEvents() {
+  received_.clear();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TestEventTarget, protected
+
+bool TestEventTarget::CanAcceptEvent(const ui::Event& event) {
+  return true;
+}
+
+EventTarget* TestEventTarget::GetParentTarget() {
+  return parent_;
+}
+
+scoped_ptr<EventTargetIterator> TestEventTarget::GetChildIterator() const {
+  return scoped_ptr<EventTargetIterator>(
+      new EventTargetIteratorImpl<TestEventTarget>(children_.get()));
+}
+
+EventTargeter* TestEventTarget::GetEventTargeter() {
+  return targeter_.get();
+}
+
+void TestEventTarget::OnEvent(Event* event) {
+  if (recorder_)
+    recorder_->push_back(target_name_);
+  received_.insert(event->type());
+  EventTarget::OnEvent(event);
+  if (!event->handled() && mark_events_as_handled_)
+    event->SetHandled();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TestEventTarget, private
+
+bool TestEventTarget::Contains(TestEventTarget* target) const {
+  while (target && target != this)
+    target = target->parent();
+  return target == this;
+}
+
+}  // namespace test
+}  // namespace ui
diff --git a/ui/events/test/test_event_target.h b/ui/events/test/test_event_target.h
new file mode 100644
index 0000000..368e295
--- /dev/null
+++ b/ui/events/test/test_event_target.h
@@ -0,0 +1,84 @@
+// Copyright 2013 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 UI_EVENTS_TEST_TEST_EVENT_TARGET_H_
+#define UI_EVENTS_TEST_TEST_EVENT_TARGET_H_
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_vector.h"
+#include "ui/events/event_target.h"
+
+typedef std::vector<std::string> HandlerSequenceRecorder;
+
+namespace gfx {
+class Point;
+}
+
+namespace ui {
+namespace test {
+
+class TestEventTarget : public EventTarget {
+ public:
+  TestEventTarget();
+  virtual ~TestEventTarget();
+
+  void AddChild(scoped_ptr<TestEventTarget> child);
+  scoped_ptr<TestEventTarget> RemoveChild(TestEventTarget* child);
+
+  TestEventTarget* parent() { return parent_; }
+
+  void set_mark_events_as_handled(bool handle) {
+    mark_events_as_handled_ = handle;
+  }
+
+  TestEventTarget* child_at(int index) { return children_[index]; }
+  size_t child_count() const { return children_.size(); }
+
+  void SetEventTargeter(scoped_ptr<EventTargeter> targeter);
+
+  bool DidReceiveEvent(ui::EventType type) const;
+  void ResetReceivedEvents();
+
+  void set_recorder(HandlerSequenceRecorder* recorder) {
+    recorder_ = recorder;
+  }
+  void set_target_name(const std::string& target_name) {
+    target_name_ = target_name;
+  }
+
+ protected:
+  bool Contains(TestEventTarget* target) const;
+
+  // EventTarget:
+  virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE;
+  virtual EventTarget* GetParentTarget() OVERRIDE;
+  virtual scoped_ptr<EventTargetIterator> GetChildIterator() const OVERRIDE;
+  virtual EventTargeter* GetEventTargeter() OVERRIDE;
+
+  // EventHandler:
+  virtual void OnEvent(Event* event) OVERRIDE;
+
+ private:
+  void set_parent(TestEventTarget* parent) { parent_ = parent; }
+
+  TestEventTarget* parent_;
+  ScopedVector<TestEventTarget> children_;
+  scoped_ptr<EventTargeter> targeter_;
+  bool mark_events_as_handled_;
+
+  std::set<ui::EventType> received_;
+
+  HandlerSequenceRecorder* recorder_;
+  std::string target_name_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestEventTarget);
+};
+
+}  // namespace test
+}  // namespace ui
+
+#endif  // UI_EVENTS_TEST_TEST_EVENT_TARGET_H_
diff --git a/ui/events/touchscreen_device.cc b/ui/events/touchscreen_device.cc
new file mode 100644
index 0000000..07844a1
--- /dev/null
+++ b/ui/events/touchscreen_device.cc
@@ -0,0 +1,18 @@
+// Copyright 2014 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 "ui/events/touchscreen_device.h"
+
+namespace ui {
+
+// static
+const int TouchscreenDevice::kInvalidId = 0;
+
+TouchscreenDevice::TouchscreenDevice(int id,
+                                     const gfx::Size& size,
+                                     bool is_internal)
+    : id(id), size(size), is_internal(is_internal) {
+}
+
+}  // namespace ui
diff --git a/ui/events/touchscreen_device.h b/ui/events/touchscreen_device.h
new file mode 100644
index 0000000..669f306
--- /dev/null
+++ b/ui/events/touchscreen_device.h
@@ -0,0 +1,31 @@
+// Copyright 2014 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 UI_EVENTS_TOUCHSCREEN_DEVICE_H_
+#define UI_EVENTS_TOUCHSCREEN_DEVICE_H_
+
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/geometry/size.h"
+
+namespace ui {
+
+// Represents a Touchscreen device state.
+struct EVENTS_BASE_EXPORT TouchscreenDevice {
+  static const int kInvalidId;
+
+  TouchscreenDevice(int id, const gfx::Size& size, bool is_internal);
+
+  // ID of the touch screen. This ID must uniquely identify the touch screen.
+  int id;
+
+  // Size of the touch screen area.
+  gfx::Size size;
+
+  // True if this is an internal touchscreen.
+  bool is_internal;
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_TOUCHSCREEN_DEVICE_H_
diff --git a/ui/events/win/events_win.cc b/ui/events/win/events_win.cc
new file mode 100644
index 0000000..9434b2b
--- /dev/null
+++ b/ui/events/win/events_win.cc
@@ -0,0 +1,414 @@
+// Copyright (c) 2012 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 <windowsx.h>
+
+#include "ui/events/event_constants.h"
+
+#include "base/logging.h"
+#include "base/time/time.h"
+#include "base/win/win_util.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion_win.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/win/dpi.h"
+
+namespace ui {
+
+namespace {
+
+// From MSDN: "Mouse" events are flagged with 0xFF515700 if they come
+// from a touch or stylus device.  In Vista or later, they are also flagged
+// with 0x80 if they come from touch.
+#define MOUSEEVENTF_FROMTOUCH (0xFF515700 | 0x80)
+
+// Get the native mouse key state from the native event message type.
+int GetNativeMouseKey(const base::NativeEvent& native_event) {
+  switch (native_event.message) {
+    case WM_LBUTTONDBLCLK:
+    case WM_LBUTTONDOWN:
+    case WM_LBUTTONUP:
+    case WM_NCLBUTTONDBLCLK:
+    case WM_NCLBUTTONDOWN:
+    case WM_NCLBUTTONUP:
+      return MK_LBUTTON;
+    case WM_MBUTTONDBLCLK:
+    case WM_MBUTTONDOWN:
+    case WM_MBUTTONUP:
+    case WM_NCMBUTTONDBLCLK:
+    case WM_NCMBUTTONDOWN:
+    case WM_NCMBUTTONUP:
+      return MK_MBUTTON;
+    case WM_RBUTTONDBLCLK:
+    case WM_RBUTTONDOWN:
+    case WM_RBUTTONUP:
+    case WM_NCRBUTTONDBLCLK:
+    case WM_NCRBUTTONDOWN:
+    case WM_NCRBUTTONUP:
+      return MK_RBUTTON;
+    case WM_NCXBUTTONDBLCLK:
+    case WM_NCXBUTTONDOWN:
+    case WM_NCXBUTTONUP:
+    case WM_XBUTTONDBLCLK:
+    case WM_XBUTTONDOWN:
+    case WM_XBUTTONUP:
+      return MK_XBUTTON1;
+  }
+  return 0;
+}
+
+bool IsButtonDown(const base::NativeEvent& native_event) {
+  return ((MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2) &
+          native_event.wParam) != 0;
+}
+
+bool IsClientMouseEvent(const base::NativeEvent& native_event) {
+  return native_event.message == WM_MOUSELEAVE ||
+         native_event.message == WM_MOUSEHOVER ||
+        (native_event.message >= WM_MOUSEFIRST &&
+         native_event.message <= WM_MOUSELAST);
+}
+
+bool IsNonClientMouseEvent(const base::NativeEvent& native_event) {
+  return native_event.message == WM_NCMOUSELEAVE ||
+         native_event.message == WM_NCMOUSEHOVER ||
+        (native_event.message >= WM_NCMOUSEMOVE &&
+         native_event.message <= WM_NCXBUTTONDBLCLK);
+}
+
+bool IsMouseEvent(const base::NativeEvent& native_event) {
+  return IsClientMouseEvent(native_event) ||
+         IsNonClientMouseEvent(native_event);
+}
+
+bool IsMouseWheelEvent(const base::NativeEvent& native_event) {
+  return native_event.message == WM_MOUSEWHEEL ||
+         native_event.message == WM_MOUSEHWHEEL;
+}
+
+bool IsKeyEvent(const base::NativeEvent& native_event) {
+  return native_event.message == WM_KEYDOWN ||
+         native_event.message == WM_SYSKEYDOWN ||
+         native_event.message == WM_CHAR ||
+         native_event.message == WM_KEYUP ||
+         native_event.message == WM_SYSKEYUP;
+}
+
+bool IsScrollEvent(const base::NativeEvent& native_event) {
+  return native_event.message == WM_VSCROLL ||
+         native_event.message == WM_HSCROLL;
+}
+
+// Returns a mask corresponding to the set of pressed modifier keys.
+// Checks the current global state and the state sent by client mouse messages.
+int KeyStateFlagsFromNative(const base::NativeEvent& native_event) {
+  int flags = 0;
+  flags |= base::win::IsAltPressed() ? EF_ALT_DOWN : EF_NONE;
+  flags |= base::win::IsShiftPressed() ? EF_SHIFT_DOWN : EF_NONE;
+  flags |= base::win::IsCtrlPressed() ? EF_CONTROL_DOWN : EF_NONE;
+
+  // Check key messages for the extended key flag.
+  if (IsKeyEvent(native_event))
+    flags |= (HIWORD(native_event.lParam) & KF_EXTENDED) ? EF_EXTENDED : 0;
+
+  // Most client mouse messages include key state information.
+  if (IsClientMouseEvent(native_event)) {
+    int win_flags = GET_KEYSTATE_WPARAM(native_event.wParam);
+    flags |= (win_flags & MK_SHIFT) ? EF_SHIFT_DOWN : 0;
+    flags |= (win_flags & MK_CONTROL) ? EF_CONTROL_DOWN : 0;
+  }
+
+  return flags;
+}
+
+// Returns a mask corresponding to the set of pressed mouse buttons.
+// This includes the button of the given message, even if it is being released.
+int MouseStateFlagsFromNative(const base::NativeEvent& native_event) {
+  int win_flags = GetNativeMouseKey(native_event);
+
+  // Client mouse messages provide key states in their WPARAMs.
+  if (IsClientMouseEvent(native_event))
+    win_flags |= GET_KEYSTATE_WPARAM(native_event.wParam);
+
+  int flags = 0;
+  flags |= (win_flags & MK_LBUTTON) ? EF_LEFT_MOUSE_BUTTON : 0;
+  flags |= (win_flags & MK_MBUTTON) ? EF_MIDDLE_MOUSE_BUTTON : 0;
+  flags |= (win_flags & MK_RBUTTON) ? EF_RIGHT_MOUSE_BUTTON : 0;
+  flags |= IsNonClientMouseEvent(native_event) ? EF_IS_NON_CLIENT : 0;
+  return flags;
+}
+
+}  // namespace
+
+void UpdateDeviceList() {
+  NOTIMPLEMENTED();
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+  switch (native_event.message) {
+    case WM_KEYDOWN:
+    case WM_SYSKEYDOWN:
+    case WM_CHAR:
+      return ET_KEY_PRESSED;
+    // The WM_DEADCHAR message is posted to the window with the keyboard focus
+    // when a WM_KEYUP message is translated. This happens for special keyboard
+    // sequences.
+    case WM_DEADCHAR:
+    case WM_KEYUP:
+    case WM_SYSKEYUP:
+      return ET_KEY_RELEASED;
+    case WM_LBUTTONDBLCLK:
+    case WM_LBUTTONDOWN:
+    case WM_MBUTTONDBLCLK:
+    case WM_MBUTTONDOWN:
+    case WM_NCLBUTTONDBLCLK:
+    case WM_NCLBUTTONDOWN:
+    case WM_NCMBUTTONDBLCLK:
+    case WM_NCMBUTTONDOWN:
+    case WM_NCRBUTTONDBLCLK:
+    case WM_NCRBUTTONDOWN:
+    case WM_NCXBUTTONDBLCLK:
+    case WM_NCXBUTTONDOWN:
+    case WM_RBUTTONDBLCLK:
+    case WM_RBUTTONDOWN:
+    case WM_XBUTTONDBLCLK:
+    case WM_XBUTTONDOWN:
+      return ET_MOUSE_PRESSED;
+    case WM_LBUTTONUP:
+    case WM_MBUTTONUP:
+    case WM_NCLBUTTONUP:
+    case WM_NCMBUTTONUP:
+    case WM_NCRBUTTONUP:
+    case WM_NCXBUTTONUP:
+    case WM_RBUTTONUP:
+    case WM_XBUTTONUP:
+      return ET_MOUSE_RELEASED;
+    case WM_MOUSEMOVE:
+      return IsButtonDown(native_event) ? ET_MOUSE_DRAGGED : ET_MOUSE_MOVED;
+    case WM_NCMOUSEMOVE:
+      return ET_MOUSE_MOVED;
+    case WM_MOUSEWHEEL:
+    case WM_MOUSEHWHEEL:
+      return ET_MOUSEWHEEL;
+    case WM_MOUSELEAVE:
+    case WM_NCMOUSELEAVE:
+      return ET_MOUSE_EXITED;
+    case WM_VSCROLL:
+    case WM_HSCROLL:
+      return ET_SCROLL;
+    default:
+      // We can't NOTREACHED() here, since this function can be called for any
+      // message.
+      break;
+  }
+  return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+  int flags = KeyStateFlagsFromNative(native_event);
+  if (IsMouseEvent(native_event))
+    flags |= MouseStateFlagsFromNative(native_event);
+
+  return flags;
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+  return base::TimeDelta::FromMilliseconds(native_event.time);
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+  POINT native_point;
+  if ((native_event.message == WM_MOUSELEAVE ||
+       native_event.message == WM_NCMOUSELEAVE) ||
+      IsScrollEvent(native_event)) {
+    // These events have no coordinates. For sanity with rest of events grab
+    // coordinates from the OS.
+    ::GetCursorPos(&native_point);
+  } else if (IsClientMouseEvent(native_event) &&
+             !IsMouseWheelEvent(native_event)) {
+    // Note: Wheel events are considered client, but their position is in screen
+    //       coordinates.
+    // Client message. The position is contained in the LPARAM.
+    return gfx::Point(native_event.lParam);
+  } else {
+    DCHECK(IsNonClientMouseEvent(native_event) ||
+           IsMouseWheelEvent(native_event) || IsScrollEvent(native_event));
+    // Non-client message. The position is contained in a POINTS structure in
+    // LPARAM, and is in screen coordinates so we have to convert to client.
+    native_point.x = GET_X_LPARAM(native_event.lParam);
+    native_point.y = GET_Y_LPARAM(native_event.lParam);
+  }
+  ScreenToClient(native_event.hwnd, &native_point);
+  return gfx::Point(native_point);
+}
+
+gfx::Point EventSystemLocationFromNative(
+    const base::NativeEvent& native_event) {
+  POINT global_point = { static_cast<short>(LOWORD(native_event.lParam)),
+                         static_cast<short>(HIWORD(native_event.lParam)) };
+  ClientToScreen(native_event.hwnd, &global_point);
+  return gfx::Point(global_point);
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+  return KeyboardCodeForWindowsKeyCode(native_event.wParam);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+  const uint16 scan_code = GetScanCodeFromLParam(native_event.lParam);
+  return CodeForWindowsScanCode(scan_code);
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+  return static_cast<uint32>(native_event.wParam);
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+  return native_event.message == WM_CHAR;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+    const base::NativeEvent& native_event) {
+  switch (GetNativeMouseKey(native_event)) {
+    case MK_LBUTTON:
+      return EF_LEFT_MOUSE_BUTTON;
+    case MK_MBUTTON:
+      return EF_MIDDLE_MOUSE_BUTTON;
+    case MK_RBUTTON:
+      return EF_RIGHT_MOUSE_BUTTON;
+    // TODO: add support for MK_XBUTTON1.
+    default:
+      break;
+  }
+  return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+  DCHECK(native_event.message == WM_MOUSEWHEEL ||
+         native_event.message == WM_MOUSEHWHEEL);
+  if (native_event.message == WM_MOUSEWHEEL)
+    return gfx::Vector2d(0, GET_WHEEL_DELTA_WPARAM(native_event.wParam));
+  return gfx::Vector2d(GET_WHEEL_DELTA_WPARAM(native_event.wParam), 0);
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+  return event;
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& event) {
+  NOTIMPLEMENTED();
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
+  NOTIMPLEMENTED();
+}
+
+int GetTouchId(const base::NativeEvent& xev) {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 1.0;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 1.0;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.0;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+  NOTIMPLEMENTED();
+  return 0.0;
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+                      float* x_offset,
+                      float* y_offset,
+                      float* x_offset_ordinal,
+                      float* y_offset_ordinal,
+                      int* finger_count) {
+  // TODO(ananta)
+  // Support retrieving the scroll offsets from the scroll event.
+  if (native_event.message == WM_VSCROLL || native_event.message == WM_HSCROLL)
+    return true;
+  return false;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+                  float* vx,
+                  float* vy,
+                  float* vx_ordinal,
+                  float* vy_ordinal,
+                  bool* is_cancel) {
+  // Not supported in Windows.
+  NOTIMPLEMENTED();
+  return false;
+}
+
+int GetModifiersFromACCEL(const ACCEL& accel) {
+  int modifiers = EF_NONE;
+  if (accel.fVirt & FSHIFT)
+    modifiers |= EF_SHIFT_DOWN;
+  if (accel.fVirt & FCONTROL)
+    modifiers |= EF_CONTROL_DOWN;
+  if (accel.fVirt & FALT)
+    modifiers |= EF_ALT_DOWN;
+  return modifiers;
+}
+
+int GetModifiersFromKeyState() {
+  int modifiers = EF_NONE;
+  if (base::win::IsShiftPressed())
+    modifiers |= EF_SHIFT_DOWN;
+  if (base::win::IsCtrlPressed())
+    modifiers |= EF_CONTROL_DOWN;
+  if (base::win::IsAltPressed())
+    modifiers |= EF_ALT_DOWN;
+  if (base::win::IsAltGrPressed())
+    modifiers |= EF_ALTGR_DOWN;
+  return modifiers;
+}
+
+// Windows emulates mouse messages for touch events.
+bool IsMouseEventFromTouch(UINT message) {
+  return (message >= WM_MOUSEFIRST) && (message <= WM_MOUSELAST) &&
+      (GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) ==
+      MOUSEEVENTF_FROMTOUCH;
+}
+
+// Conversion scan_code and LParam each other.
+// uint16 scan_code:
+//     ui/events/keycodes/dom4/keycode_converter_data.h
+// 0 - 15bits: represetns the scan code.
+// 28 - 30 bits (0xE000): represents whether this is an extended key or not.
+//
+// LPARAM lParam:
+//     http://msdn.microsoft.com/en-us/library/windows/desktop/ms644984.aspx
+// 16 - 23bits: represetns the scan code.
+// 24bit (0x0100): represents whether this is an extended key or not.
+uint16 GetScanCodeFromLParam(LPARAM l_param) {
+  uint16 scan_code = ((l_param >> 16) & 0x00FF);
+  if (l_param & (1 << 24))
+    scan_code |= 0xE000;
+  return scan_code;
+}
+
+LPARAM GetLParamFromScanCode(uint16 scan_code) {
+  LPARAM l_param = static_cast<LPARAM>(scan_code & 0x00FF) << 16;
+  if ((scan_code & 0xE000) == 0xE000)
+    l_param |= (1 << 24);
+  return l_param;
+}
+
+}  // namespace ui
diff --git a/ui/events/x/device_data_manager_x11.cc b/ui/events/x/device_data_manager_x11.cc
new file mode 100644
index 0000000..795cc44
--- /dev/null
+++ b/ui/events/x/device_data_manager_x11.cc
@@ -0,0 +1,710 @@
+// Copyright 2014 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 "ui/events/x/device_data_manager_x11.h"
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/sys_info.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_switches.h"
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include "ui/events/x/device_list_cache_x.h"
+#include "ui/events/x/touch_factory_x11.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/point3_f.h"
+#include "ui/gfx/x/x11_types.h"
+
+// XIScrollClass was introduced in XI 2.1 so we need to define it here
+// for backward-compatibility with older versions of XInput.
+#if !defined(XIScrollClass)
+#define XIScrollClass 3
+#endif
+
+// Multi-touch support was introduced in XI 2.2. Add XI event types here
+// for backward-compatibility with older versions of XInput.
+#if !defined(XI_TouchBegin)
+#define XI_TouchBegin  18
+#define XI_TouchUpdate 19
+#define XI_TouchEnd    20
+#endif
+
+// Copied from xserver-properties.h
+#define AXIS_LABEL_PROP_REL_HWHEEL "Rel Horiz Wheel"
+#define AXIS_LABEL_PROP_REL_WHEEL "Rel Vert Wheel"
+
+// CMT specific timings
+#define AXIS_LABEL_PROP_ABS_DBL_START_TIME "Abs Dbl Start Timestamp"
+#define AXIS_LABEL_PROP_ABS_DBL_END_TIME   "Abs Dbl End Timestamp"
+
+// Ordinal values
+#define AXIS_LABEL_PROP_ABS_DBL_ORDINAL_X   "Abs Dbl Ordinal X"
+#define AXIS_LABEL_PROP_ABS_DBL_ORDINAL_Y   "Abs Dbl Ordinal Y"
+
+// Fling properties
+#define AXIS_LABEL_PROP_ABS_DBL_FLING_VX   "Abs Dbl Fling X Velocity"
+#define AXIS_LABEL_PROP_ABS_DBL_FLING_VY   "Abs Dbl Fling Y Velocity"
+#define AXIS_LABEL_PROP_ABS_FLING_STATE   "Abs Fling State"
+
+#define AXIS_LABEL_PROP_ABS_FINGER_COUNT   "Abs Finger Count"
+
+// Cros metrics gesture from touchpad
+#define AXIS_LABEL_PROP_ABS_METRICS_TYPE      "Abs Metrics Type"
+#define AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA1 "Abs Dbl Metrics Data 1"
+#define AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA2 "Abs Dbl Metrics Data 2"
+
+// Touchscreen multi-touch
+#define AXIS_LABEL_ABS_MT_TOUCH_MAJOR "Abs MT Touch Major"
+#define AXIS_LABEL_ABS_MT_TOUCH_MINOR "Abs MT Touch Minor"
+#define AXIS_LABEL_ABS_MT_ORIENTATION "Abs MT Orientation"
+#define AXIS_LABEL_ABS_MT_PRESSURE    "Abs MT Pressure"
+#define AXIS_LABEL_ABS_MT_POSITION_X  "Abs MT Position X"
+#define AXIS_LABEL_ABS_MT_POSITION_Y  "Abs MT Position Y"
+#define AXIS_LABEL_ABS_MT_TRACKING_ID "Abs MT Tracking ID"
+#define AXIS_LABEL_TOUCH_TIMESTAMP    "Touch Timestamp"
+
+// When you add new data types, please make sure the order here is aligned
+// with the order in the DataType enum in the header file because we assume
+// they are in sync when updating the device list (see UpdateDeviceList).
+const char* kCachedAtoms[] = {
+  AXIS_LABEL_PROP_REL_HWHEEL,
+  AXIS_LABEL_PROP_REL_WHEEL,
+  AXIS_LABEL_PROP_ABS_DBL_ORDINAL_X,
+  AXIS_LABEL_PROP_ABS_DBL_ORDINAL_Y,
+  AXIS_LABEL_PROP_ABS_DBL_START_TIME,
+  AXIS_LABEL_PROP_ABS_DBL_END_TIME,
+  AXIS_LABEL_PROP_ABS_DBL_FLING_VX,
+  AXIS_LABEL_PROP_ABS_DBL_FLING_VY,
+  AXIS_LABEL_PROP_ABS_FLING_STATE,
+  AXIS_LABEL_PROP_ABS_METRICS_TYPE,
+  AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA1,
+  AXIS_LABEL_PROP_ABS_DBL_METRICS_DATA2,
+  AXIS_LABEL_PROP_ABS_FINGER_COUNT,
+  AXIS_LABEL_ABS_MT_TOUCH_MAJOR,
+  AXIS_LABEL_ABS_MT_TOUCH_MINOR,
+  AXIS_LABEL_ABS_MT_ORIENTATION,
+  AXIS_LABEL_ABS_MT_PRESSURE,
+  AXIS_LABEL_ABS_MT_POSITION_X,
+  AXIS_LABEL_ABS_MT_POSITION_Y,
+  AXIS_LABEL_ABS_MT_TRACKING_ID,
+  AXIS_LABEL_TOUCH_TIMESTAMP,
+
+  NULL
+};
+
+// Constants for checking if a data type lies in the range of CMT/Touch data
+// types.
+const int kCMTDataTypeStart = ui::DeviceDataManagerX11::DT_CMT_SCROLL_X;
+const int kCMTDataTypeEnd = ui::DeviceDataManagerX11::DT_CMT_FINGER_COUNT;
+const int kTouchDataTypeStart = ui::DeviceDataManagerX11::DT_TOUCH_MAJOR;
+const int kTouchDataTypeEnd = ui::DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP;
+
+namespace ui {
+
+bool DeviceDataManagerX11::IsCMTDataType(const int type) {
+  return (type >= kCMTDataTypeStart) && (type <= kCMTDataTypeEnd);
+}
+
+bool DeviceDataManagerX11::IsTouchDataType(const int type) {
+  return (type >= kTouchDataTypeStart) && (type <= kTouchDataTypeEnd);
+}
+
+// static
+void DeviceDataManagerX11::CreateInstance() {
+  if (instance())
+    return;
+
+  new DeviceDataManagerX11();
+}
+
+// static
+DeviceDataManagerX11* DeviceDataManagerX11::GetInstance() {
+  return static_cast<DeviceDataManagerX11*>(DeviceDataManager::GetInstance());
+}
+
+DeviceDataManagerX11::DeviceDataManagerX11()
+    : xi_opcode_(-1),
+      atom_cache_(gfx::GetXDisplay(), kCachedAtoms),
+      button_map_count_(0) {
+  CHECK(gfx::GetXDisplay());
+  InitializeXInputInternal();
+
+  // Make sure the sizes of enum and kCachedAtoms are aligned.
+  CHECK(arraysize(kCachedAtoms) == static_cast<size_t>(DT_LAST_ENTRY) + 1);
+  UpdateDeviceList(gfx::GetXDisplay());
+  UpdateButtonMap();
+}
+
+DeviceDataManagerX11::~DeviceDataManagerX11() {
+}
+
+bool DeviceDataManagerX11::InitializeXInputInternal() {
+  // Check if XInput is available on the system.
+  xi_opcode_ = -1;
+  int opcode, event, error;
+  if (!XQueryExtension(
+      gfx::GetXDisplay(), "XInputExtension", &opcode, &event, &error)) {
+    VLOG(1) << "X Input extension not available: error=" << error;
+    return false;
+  }
+
+  // Check the XInput version.
+#if defined(USE_XI2_MT)
+  int major = 2, minor = USE_XI2_MT;
+#else
+  int major = 2, minor = 0;
+#endif
+  if (XIQueryVersion(gfx::GetXDisplay(), &major, &minor) == BadRequest) {
+    VLOG(1) << "XInput2 not supported in the server.";
+    return false;
+  }
+#if defined(USE_XI2_MT)
+  if (major < 2 || (major == 2 && minor < USE_XI2_MT)) {
+    DVLOG(1) << "XI version on server is " << major << "." << minor << ". "
+            << "But 2." << USE_XI2_MT << " is required.";
+    return false;
+  }
+#endif
+
+  xi_opcode_ = opcode;
+  CHECK_NE(-1, xi_opcode_);
+
+  // Possible XI event types for XIDeviceEvent. See the XI2 protocol
+  // specification.
+  xi_device_event_types_[XI_KeyPress] = true;
+  xi_device_event_types_[XI_KeyRelease] = true;
+  xi_device_event_types_[XI_ButtonPress] = true;
+  xi_device_event_types_[XI_ButtonRelease] = true;
+  xi_device_event_types_[XI_Motion] = true;
+  // Multi-touch support was introduced in XI 2.2.
+  if (minor >= 2) {
+    xi_device_event_types_[XI_TouchBegin] = true;
+    xi_device_event_types_[XI_TouchUpdate] = true;
+    xi_device_event_types_[XI_TouchEnd] = true;
+  }
+  return true;
+}
+
+bool DeviceDataManagerX11::IsXInput2Available() const {
+  return xi_opcode_ != -1;
+}
+
+void DeviceDataManagerX11::UpdateDeviceList(Display* display) {
+  cmt_devices_.reset();
+  touchpads_.reset();
+  for (int i = 0; i < kMaxDeviceNum; ++i) {
+    valuator_count_[i] = 0;
+    valuator_lookup_[i].clear();
+    data_type_lookup_[i].clear();
+    valuator_min_[i].clear();
+    valuator_max_[i].clear();
+    for (int j = 0; j < kMaxSlotNum; j++)
+      last_seen_valuator_[i][j].clear();
+  }
+
+  // Find all the touchpad devices.
+  XDeviceList dev_list =
+      ui::DeviceListCacheX::GetInstance()->GetXDeviceList(display);
+  Atom xi_touchpad = XInternAtom(display, XI_TOUCHPAD, false);
+  for (int i = 0; i < dev_list.count; ++i)
+    if (dev_list[i].type == xi_touchpad)
+      touchpads_[dev_list[i].id] = true;
+
+  if (!IsXInput2Available())
+    return;
+
+  // Update the structs with new valuator information
+  XIDeviceList info_list =
+      ui::DeviceListCacheX::GetInstance()->GetXI2DeviceList(display);
+  Atom atoms[DT_LAST_ENTRY];
+  for (int data_type = 0; data_type < DT_LAST_ENTRY; ++data_type)
+    atoms[data_type] = atom_cache_.GetAtom(kCachedAtoms[data_type]);
+
+  for (int i = 0; i < info_list.count; ++i) {
+    XIDeviceInfo* info = info_list.devices + i;
+
+    // We currently handle only slave, non-keyboard devices
+    if (info->use != XISlavePointer && info->use != XIFloatingSlave)
+      continue;
+
+    bool possible_cmt = false;
+    bool not_cmt = false;
+    const int deviceid = info->deviceid;
+
+    for (int j = 0; j < info->num_classes; ++j) {
+      if (info->classes[j]->type == XIValuatorClass)
+        ++valuator_count_[deviceid];
+      else if (info->classes[j]->type == XIScrollClass)
+        not_cmt = true;
+    }
+
+    // Skip devices that don't use any valuator
+    if (!valuator_count_[deviceid])
+      continue;
+
+    valuator_lookup_[deviceid].resize(DT_LAST_ENTRY, -1);
+    data_type_lookup_[deviceid].resize(
+        valuator_count_[deviceid], DT_LAST_ENTRY);
+    valuator_min_[deviceid].resize(DT_LAST_ENTRY, 0);
+    valuator_max_[deviceid].resize(DT_LAST_ENTRY, 0);
+    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 (possible_cmt && !not_cmt)
+      cmt_devices_[deviceid] = true;
+  }
+}
+
+bool DeviceDataManagerX11::GetSlotNumber(const XIDeviceEvent* xiev, int* slot) {
+#if defined(USE_XI2_MT)
+  ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+  if (!factory->IsMultiTouchDevice(xiev->sourceid)) {
+    *slot = 0;
+    return true;
+  }
+  return factory->QuerySlotForTrackingID(xiev->detail, slot);
+#else
+  *slot = 0;
+  return true;
+#endif
+}
+
+void DeviceDataManagerX11::GetEventRawData(const XEvent& xev, EventData* data) {
+  if (xev.type != GenericEvent)
+    return;
+
+  XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
+  if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
+    return;
+  data->clear();
+  const int sourceid = xiev->sourceid;
+  double* valuators = xiev->valuators.values;
+  for (int i = 0; i <= valuator_count_[sourceid]; ++i) {
+    if (XIMaskIsSet(xiev->valuators.mask, i)) {
+      int type = data_type_lookup_[sourceid][i];
+      if (type != DT_LAST_ENTRY) {
+        (*data)[type] = *valuators;
+        if (IsTouchDataType(type)) {
+          int slot = -1;
+          if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
+            last_seen_valuator_[sourceid][slot][type] = *valuators;
+        }
+      }
+      valuators++;
+    }
+  }
+}
+
+bool DeviceDataManagerX11::GetEventData(const XEvent& xev,
+    const DataType type, double* value) {
+  if (xev.type != GenericEvent)
+    return false;
+
+  XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev.xcookie.data);
+  if (xiev->sourceid >= kMaxDeviceNum || xiev->deviceid >= kMaxDeviceNum)
+    return false;
+  const int sourceid = xiev->sourceid;
+  if (valuator_lookup_[sourceid].empty())
+    return false;
+
+  if (type == DT_TOUCH_TRACKING_ID) {
+    // With XInput2 MT, Tracking ID is provided in the detail field for touch
+    // events.
+    if (xiev->evtype == XI_TouchBegin ||
+        xiev->evtype == XI_TouchEnd ||
+        xiev->evtype == XI_TouchUpdate) {
+      *value = xiev->detail;
+    } else {
+      *value = 0;
+    }
+    return true;
+  }
+
+  int val_index = valuator_lookup_[sourceid][type];
+  int slot = 0;
+  if (val_index >= 0) {
+    if (XIMaskIsSet(xiev->valuators.mask, val_index)) {
+      double* valuators = xiev->valuators.values;
+      while (val_index--) {
+        if (XIMaskIsSet(xiev->valuators.mask, val_index))
+          ++valuators;
+      }
+      *value = *valuators;
+      if (IsTouchDataType(type)) {
+        if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
+          last_seen_valuator_[sourceid][slot][type] = *value;
+      }
+      return true;
+    } else if (IsTouchDataType(type)) {
+      if (GetSlotNumber(xiev, &slot) && slot >= 0 && slot < kMaxSlotNum)
+        *value = last_seen_valuator_[sourceid][slot][type];
+    }
+  }
+
+  return false;
+}
+
+bool DeviceDataManagerX11::IsXIDeviceEvent(
+    const base::NativeEvent& native_event) const {
+  if (native_event->type != GenericEvent ||
+      native_event->xcookie.extension != xi_opcode_)
+    return false;
+  return xi_device_event_types_[native_event->xcookie.evtype];
+}
+
+bool DeviceDataManagerX11::IsTouchpadXInputEvent(
+    const base::NativeEvent& native_event) const {
+  if (native_event->type != GenericEvent)
+    return false;
+
+  XIDeviceEvent* xievent =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  if (xievent->sourceid >= kMaxDeviceNum)
+    return false;
+  return touchpads_[xievent->sourceid];
+}
+
+bool DeviceDataManagerX11::IsCMTDeviceEvent(
+    const base::NativeEvent& native_event) const {
+  if (native_event->type != GenericEvent)
+    return false;
+
+  XIDeviceEvent* xievent =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  if (xievent->sourceid >= kMaxDeviceNum)
+    return false;
+  return cmt_devices_[xievent->sourceid];
+}
+
+bool DeviceDataManagerX11::IsCMTGestureEvent(
+    const base::NativeEvent& native_event) const {
+  return (IsScrollEvent(native_event) ||
+          IsFlingEvent(native_event) ||
+          IsCMTMetricsEvent(native_event));
+}
+
+bool DeviceDataManagerX11::HasEventData(
+    const XIDeviceEvent* xiev, const DataType type) const {
+  const int idx = valuator_lookup_[xiev->sourceid][type];
+  return (idx >= 0) && XIMaskIsSet(xiev->valuators.mask, idx);
+}
+
+bool DeviceDataManagerX11::IsScrollEvent(
+    const base::NativeEvent& native_event) const {
+  if (!IsCMTDeviceEvent(native_event))
+    return false;
+
+  XIDeviceEvent* xiev =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  return (HasEventData(xiev, DT_CMT_SCROLL_X) ||
+          HasEventData(xiev, DT_CMT_SCROLL_Y));
+}
+
+bool DeviceDataManagerX11::IsFlingEvent(
+    const base::NativeEvent& native_event) const {
+  if (!IsCMTDeviceEvent(native_event))
+    return false;
+
+  XIDeviceEvent* xiev =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  return (HasEventData(xiev, DT_CMT_FLING_X) &&
+          HasEventData(xiev, DT_CMT_FLING_Y) &&
+          HasEventData(xiev, DT_CMT_FLING_STATE));
+}
+
+bool DeviceDataManagerX11::IsCMTMetricsEvent(
+    const base::NativeEvent& native_event) const {
+  if (!IsCMTDeviceEvent(native_event))
+    return false;
+
+  XIDeviceEvent* xiev =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  return (HasEventData(xiev, DT_CMT_METRICS_TYPE) &&
+          HasEventData(xiev, DT_CMT_METRICS_DATA1) &&
+          HasEventData(xiev, DT_CMT_METRICS_DATA2));
+}
+
+bool DeviceDataManagerX11::HasGestureTimes(
+    const base::NativeEvent& native_event) const {
+  if (!IsCMTDeviceEvent(native_event))
+    return false;
+
+  XIDeviceEvent* xiev =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  return (HasEventData(xiev, DT_CMT_START_TIME) &&
+          HasEventData(xiev, DT_CMT_END_TIME));
+}
+
+void DeviceDataManagerX11::GetScrollOffsets(
+    const base::NativeEvent& native_event,
+    float* x_offset,
+    float* y_offset,
+    float* x_offset_ordinal,
+    float* y_offset_ordinal,
+    int* finger_count) {
+  *x_offset = 0;
+  *y_offset = 0;
+  *x_offset_ordinal = 0;
+  *y_offset_ordinal = 0;
+  *finger_count = 2;
+
+  EventData data;
+  GetEventRawData(*native_event, &data);
+
+  if (data.find(DT_CMT_SCROLL_X) != data.end())
+    *x_offset = data[DT_CMT_SCROLL_X];
+  if (data.find(DT_CMT_SCROLL_Y) != data.end())
+    *y_offset = data[DT_CMT_SCROLL_Y];
+  if (data.find(DT_CMT_ORDINAL_X) != data.end())
+    *x_offset_ordinal = data[DT_CMT_ORDINAL_X];
+  if (data.find(DT_CMT_ORDINAL_Y) != data.end())
+    *y_offset_ordinal = data[DT_CMT_ORDINAL_Y];
+  if (data.find(DT_CMT_FINGER_COUNT) != data.end())
+    *finger_count = static_cast<int>(data[DT_CMT_FINGER_COUNT]);
+}
+
+void DeviceDataManagerX11::GetFlingData(
+    const base::NativeEvent& native_event,
+    float* vx,
+    float* vy,
+    float* vx_ordinal,
+    float* vy_ordinal,
+    bool* is_cancel) {
+  *vx = 0;
+  *vy = 0;
+  *vx_ordinal = 0;
+  *vy_ordinal = 0;
+  *is_cancel = false;
+
+  EventData data;
+  GetEventRawData(*native_event, &data);
+
+  if (data.find(DT_CMT_FLING_X) != data.end())
+    *vx = data[DT_CMT_FLING_X];
+  if (data.find(DT_CMT_FLING_Y) != data.end())
+    *vy = data[DT_CMT_FLING_Y];
+  if (data.find(DT_CMT_FLING_STATE) != data.end())
+    *is_cancel = !!static_cast<unsigned int>(data[DT_CMT_FLING_STATE]);
+  if (data.find(DT_CMT_ORDINAL_X) != data.end())
+    *vx_ordinal = data[DT_CMT_ORDINAL_X];
+  if (data.find(DT_CMT_ORDINAL_Y) != data.end())
+    *vy_ordinal = data[DT_CMT_ORDINAL_Y];
+}
+
+void DeviceDataManagerX11::GetMetricsData(
+    const base::NativeEvent& native_event,
+    GestureMetricsType* type,
+    float* data1,
+    float* data2) {
+  *type = kGestureMetricsTypeUnknown;
+  *data1 = 0;
+  *data2 = 0;
+
+  EventData data;
+  GetEventRawData(*native_event, &data);
+
+  if (data.find(DT_CMT_METRICS_TYPE) != data.end()) {
+    int val = static_cast<int>(data[DT_CMT_METRICS_TYPE]);
+    if (val == 0)
+      *type = kGestureMetricsTypeNoisyGround;
+    else
+      *type = kGestureMetricsTypeUnknown;
+  }
+  if (data.find(DT_CMT_METRICS_DATA1) != data.end())
+    *data1 = data[DT_CMT_METRICS_DATA1];
+  if (data.find(DT_CMT_METRICS_DATA2) != data.end())
+    *data2 = data[DT_CMT_METRICS_DATA2];
+}
+
+int DeviceDataManagerX11::GetMappedButton(int button) {
+  return button > 0 && button <= button_map_count_ ? button_map_[button - 1] :
+                                                     button;
+}
+
+void DeviceDataManagerX11::UpdateButtonMap() {
+  button_map_count_ = XGetPointerMapping(gfx::GetXDisplay(),
+                                         button_map_,
+                                         arraysize(button_map_));
+}
+
+void DeviceDataManagerX11::GetGestureTimes(
+    const base::NativeEvent& native_event,
+    double* start_time,
+    double* end_time) {
+  *start_time = 0;
+  *end_time = 0;
+
+  EventData data;
+  GetEventRawData(*native_event, &data);
+
+  if (data.find(DT_CMT_START_TIME) != data.end())
+    *start_time = data[DT_CMT_START_TIME];
+  if (data.find(DT_CMT_END_TIME) != data.end())
+    *end_time = data[DT_CMT_END_TIME];
+}
+
+bool DeviceDataManagerX11::NormalizeData(unsigned int deviceid,
+                                         const DataType type,
+                                         double* value) {
+  double max_value;
+  double min_value;
+  if (GetDataRange(deviceid, type, &min_value, &max_value)) {
+    *value = (*value - min_value) / (max_value - min_value);
+    DCHECK(*value >= 0.0 && *value <= 1.0);
+    return true;
+  }
+  return false;
+}
+
+bool DeviceDataManagerX11::GetDataRange(unsigned int deviceid,
+                                        const DataType type,
+                                        double* min,
+                                        double* max) {
+  if (deviceid >= static_cast<unsigned int>(kMaxDeviceNum))
+    return false;
+  if (valuator_lookup_[deviceid][type] >= 0) {
+    *min = valuator_min_[deviceid][type];
+    *max = valuator_max_[deviceid][type];
+    return true;
+  }
+  return false;
+}
+
+void DeviceDataManagerX11::SetDeviceListForTest(
+    const std::vector<unsigned int>& touchscreen,
+    const std::vector<unsigned int>& cmt_devices) {
+  for (int i = 0; i < kMaxDeviceNum; ++i) {
+    valuator_count_[i] = 0;
+    valuator_lookup_[i].clear();
+    data_type_lookup_[i].clear();
+    valuator_min_[i].clear();
+    valuator_max_[i].clear();
+    for (int j = 0; j < kMaxSlotNum; j++)
+      last_seen_valuator_[i][j].clear();
+  }
+
+  for (size_t i = 0; i < touchscreen.size(); i++) {
+    unsigned int deviceid = touchscreen[i];
+    InitializeValuatorsForTest(deviceid, kTouchDataTypeStart, kTouchDataTypeEnd,
+                               0, 1000);
+  }
+
+  cmt_devices_.reset();
+  for (size_t i = 0; i < cmt_devices.size(); ++i) {
+    unsigned int deviceid = cmt_devices[i];
+    cmt_devices_[deviceid] = true;
+    touchpads_[deviceid] = true;
+    InitializeValuatorsForTest(deviceid, kCMTDataTypeStart, kCMTDataTypeEnd,
+                               -1000, 1000);
+  }
+}
+
+void DeviceDataManagerX11::SetValuatorDataForTest(XIDeviceEvent* xievent,
+                                                  DataType type,
+                                                  double value) {
+  int index = valuator_lookup_[xievent->deviceid][type];
+  CHECK(!XIMaskIsSet(xievent->valuators.mask, index));
+  CHECK(index >= 0 && index < valuator_count_[xievent->deviceid]);
+  XISetMask(xievent->valuators.mask, index);
+
+  double* valuators = xievent->valuators.values;
+  for (int i = 0; i < index; ++i) {
+    if (XIMaskIsSet(xievent->valuators.mask, i))
+      valuators++;
+  }
+  for (int i = DT_LAST_ENTRY - 1; i > valuators - xievent->valuators.values;
+       --i)
+    xievent->valuators.values[i] = xievent->valuators.values[i - 1];
+  *valuators = value;
+}
+
+void DeviceDataManagerX11::InitializeValuatorsForTest(int deviceid,
+                                                      int start_valuator,
+                                                      int end_valuator,
+                                                      double min_value,
+                                                      double max_value) {
+  valuator_lookup_[deviceid].resize(DT_LAST_ENTRY, -1);
+  data_type_lookup_[deviceid].resize(DT_LAST_ENTRY, DT_LAST_ENTRY);
+  valuator_min_[deviceid].resize(DT_LAST_ENTRY, 0);
+  valuator_max_[deviceid].resize(DT_LAST_ENTRY, 0);
+  for (int j = 0; j < kMaxSlotNum; j++)
+    last_seen_valuator_[deviceid][j].resize(DT_LAST_ENTRY, 0);
+  for (int j = start_valuator; j <= end_valuator; ++j) {
+    valuator_lookup_[deviceid][j] = valuator_count_[deviceid];
+    data_type_lookup_[deviceid][valuator_count_[deviceid]] = j;
+    valuator_min_[deviceid][j] = min_value;
+    valuator_max_[deviceid][j] = max_value;
+    valuator_count_[deviceid]++;
+  }
+}
+
+bool DeviceDataManagerX11::TouchEventNeedsCalibrate(int touch_device_id) const {
+#if defined(OS_CHROMEOS) && defined(USE_XI2_MT)
+  int64 touch_display_id = GetDisplayForTouchDevice(touch_device_id);
+  if (base::SysInfo::IsRunningOnChromeOS() &&
+      touch_display_id == gfx::Display::InternalDisplayId()) {
+    return true;
+  }
+#endif  // defined(OS_CHROMEOS) && defined(USE_XI2_MT)
+  return false;
+}
+
+void DeviceDataManagerX11::SetDisabledKeyboardAllowedKeys(
+    scoped_ptr<std::set<KeyboardCode> > excepted_keys) {
+  DCHECK(!excepted_keys.get() ||
+         !blocked_keyboard_allowed_keys_.get());
+  blocked_keyboard_allowed_keys_ = excepted_keys.Pass();
+}
+
+void DeviceDataManagerX11::DisableDevice(unsigned int deviceid) {
+  blocked_devices_.set(deviceid, true);
+}
+
+void DeviceDataManagerX11::EnableDevice(unsigned int deviceid) {
+  blocked_devices_.set(deviceid, false);
+}
+
+bool DeviceDataManagerX11::IsEventBlocked(
+    const base::NativeEvent& native_event) {
+  // Only check XI2 events which have a source device id.
+  if (native_event->type != GenericEvent)
+    return false;
+
+  XIDeviceEvent* xievent =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  // Allow any key events from blocked_keyboard_allowed_keys_.
+  if (blocked_keyboard_allowed_keys_ &&
+      (xievent->evtype == XI_KeyPress || xievent->evtype == XI_KeyRelease) &&
+      blocked_keyboard_allowed_keys_->find(
+          KeyboardCodeFromXKeyEvent(native_event)) !=
+          blocked_keyboard_allowed_keys_->end()) {
+    return false;
+  }
+
+  return blocked_devices_.test(xievent->sourceid);
+}
+
+}  // namespace ui
diff --git a/ui/events/x/device_data_manager_x11.h b/ui/events/x/device_data_manager_x11.h
new file mode 100644
index 0000000..6ceff06
--- /dev/null
+++ b/ui/events/x/device_data_manager_x11.h
@@ -0,0 +1,314 @@
+// Copyright 2014 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 UI_EVENTS_X_DEVICE_DATA_MANAGER_X11_H_
+#define UI_EVENTS_X_DEVICE_DATA_MANAGER_X11_H_
+
+// Generically-named #defines from Xlib is conflicting with symbols in GTest.
+// So many tests .cc file #undef Bool before including device_data_manager.h,
+// which makes Bool unrecognized in XInput2.h.
+#ifndef Bool
+#define Bool int
+#endif
+
+#include <X11/extensions/XInput2.h>
+
+#include <bitset>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/event_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/device_data_manager.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/events_base_export.h"
+#include "ui/events/keycodes/keyboard_codes.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+
+typedef union _XEvent XEvent;
+
+namespace ui {
+
+// CrOS touchpad metrics gesture types
+enum GestureMetricsType {
+  kGestureMetricsTypeNoisyGround = 0,
+  kGestureMetricsTypeUnknown,
+};
+
+// A class that extracts and tracks the input events data. It currently handles
+// mouse, touchpad and touchscreen devices.
+class EVENTS_BASE_EXPORT DeviceDataManagerX11 : public DeviceDataManager {
+ public:
+  // Enumerate additional data that one might be interested on an input event,
+  // which are usually wrapped in X valuators. If you modify any of this,
+  // make sure to update the kCachedAtoms data structure in the source file
+  // and the k*Type[Start/End] constants used by IsCMTDataType and
+  // IsTouchDataType.
+  enum DataType {
+    // Define the valuators used the CrOS CMT driver. Used by mice and CrOS
+    // touchpads.
+    DT_CMT_SCROLL_X = 0,  // Scroll amount on the X (horizontal) direction.
+    DT_CMT_SCROLL_Y,      // Scroll amount on the Y (vertical) direction.
+    DT_CMT_ORDINAL_X,     // Original (unaccelerated) value on the X direction.
+                          // Can be used both for scrolls and flings.
+    DT_CMT_ORDINAL_Y,     // Original (unaccelerated) value on the Y direction.
+                          // Can be used both for scrolls and flings.
+    DT_CMT_START_TIME,    // Gesture start time.
+    DT_CMT_END_TIME,      // Gesture end time.
+    DT_CMT_FLING_X,       // Fling amount on the X (horizontal) direction.
+    DT_CMT_FLING_Y,       // Fling amount on the Y (vertical) direction.
+    DT_CMT_FLING_STATE,   // The state of fling gesture (whether the user just
+                          // start flinging or that he/she taps down).
+    DT_CMT_METRICS_TYPE,  // Metrics type of the metrics gesture, which are
+                          // used to wrap interesting patterns that we would
+                          // like to track via the UMA system.
+    DT_CMT_METRICS_DATA1, // Complementary data 1 of the metrics gesture.
+    DT_CMT_METRICS_DATA2, // Complementary data 2 of the metrics gesture.
+    DT_CMT_FINGER_COUNT,  // Finger counts in the current gesture. A same type
+                          // of gesture can have very different meanings based
+                          // on that (e.g. 2f scroll v.s. 3f swipe).
+
+    // End of CMT data types.
+    // Beginning of touch data types.
+
+    // Define the valuators following the Multi-touch Protocol. Used by
+    // touchscreen devices.
+    DT_TOUCH_MAJOR,       // Length of the touch area.
+    DT_TOUCH_MINOR,       // Width of the touch area.
+    DT_TOUCH_ORIENTATION, // Angle between the X-axis and the major axis of the
+                          // touch area.
+    DT_TOUCH_PRESSURE,    // Pressure of the touch contact.
+
+    DT_TOUCH_POSITION_X,  // Touch X position.
+    DT_TOUCH_POSITION_Y,  // Touch Y position.
+
+    // NOTE for XInput MT: 'Tracking ID' is provided in every touch event to
+    // track individual touch. 'Tracking ID' is an unsigned 32-bit value and
+    // is increased for each new touch. It will wrap back to 0 when reaching
+    // the numerical limit.
+    DT_TOUCH_TRACKING_ID, // ID of the touch point.
+
+    // Kernel timestamp from touch screen (if available).
+    DT_TOUCH_RAW_TIMESTAMP,
+
+    // End of touch data types.
+
+    DT_LAST_ENTRY         // This must come last.
+  };
+
+  // Data struct to store extracted data from an input event.
+  typedef std::map<int, double> EventData;
+
+  static void CreateInstance();
+
+  // We use int because enums can be casted to ints but not vice versa.
+  static bool IsCMTDataType(const int type);
+  static bool IsTouchDataType(const int type);
+
+  // Returns the DeviceDataManagerX11 singleton.
+  static DeviceDataManagerX11* GetInstance();
+
+  // Returns if XInput2 is available on the system.
+  bool IsXInput2Available() const;
+
+  // Updates the list of devices.
+  void UpdateDeviceList(Display* display);
+
+  // For multitouch events we use slot number to distinguish touches from
+  // different fingers. This function returns true if the associated slot
+  // for |xiev| can be found and it is saved in |slot|, returns false if
+  // no slot can be found.
+  bool GetSlotNumber(const XIDeviceEvent* xiev, int* slot);
+
+  // Get all event data in one pass. We extract only data types that we know
+  // about (defined in enum DataType). The data is not processed (e.g. not
+  // filled in by cached values) as in GetEventData.
+  void GetEventRawData(const XEvent& xev, EventData* data);
+
+  // Get a datum of the specified type. Return true and the value
+  // is updated if the data is found, false and value unchanged if the data is
+  // not found. In the case of MT-B/XI2.2, the value can come from a previously
+  // cached one (see the comment above last_seen_valuator_).
+  bool GetEventData(const XEvent& xev, const DataType type, double* value);
+
+  // Check if the event is an XI input event in the strict sense
+  // (i.e. XIDeviceEvent). This rules out things like hierarchy changes,
+  /// device changes, property changes and so on.
+  bool IsXIDeviceEvent(const base::NativeEvent& native_event) const;
+
+  // Check if the event comes from touchpad devices.
+  bool IsTouchpadXInputEvent(const base::NativeEvent& native_event) const;
+
+  // Check if the event comes from devices running CMT driver or using
+  // CMT valuators (e.g. mouses). Note that doesn't necessarily mean the event
+  // 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 is one of the CMT gesture events (scroll, fling,
+  // metrics etc.).
+  bool IsCMTGestureEvent(const base::NativeEvent& native_event) const;
+
+  // Returns true if the event is of the specific type, false if not.
+  bool IsScrollEvent(const base::NativeEvent& native_event) const;
+  bool IsFlingEvent(const base::NativeEvent& native_event) const;
+  bool IsCMTMetricsEvent(const base::NativeEvent& native_event) const;
+
+  // Returns true if the event has CMT start/end timestamps.
+  bool HasGestureTimes(const base::NativeEvent& native_event) const;
+
+  // Extract data from a scroll event (a motion event with the necessary
+  // valuators). User must first verify the event type with IsScrollEvent.
+  // Pointers shouldn't be NULL.
+  void GetScrollOffsets(const base::NativeEvent& native_event,
+                        float* x_offset,
+                        float* y_offset,
+                        float* x_offset_ordinal,
+                        float* y_offset_ordinal,
+                        int* finger_count);
+
+  // 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,
+                    float* vx,
+                    float* vy,
+                    float* vx_ordinal,
+                    float* vy_ordinal,
+                    bool* is_cancel);
+
+  // Extract data from a CrOS metrics gesture event. User must first verify
+  // the event type with IsCMTMetricsEvent. Pointers shouldn't be NULL.
+  void GetMetricsData(const base::NativeEvent& native_event,
+                      GestureMetricsType* type,
+                      float* data1,
+                      float* data2);
+
+  // Returns the mapped button.
+  int GetMappedButton(int button);
+
+  // Updates button mapping. This is usually called when a MappingNotify event
+  // is received.
+  void UpdateButtonMap();
+
+  // Extract the start/end timestamps from CMT events. User must first verify
+  // the event with HasGestureTimes. Pointers shouldn't be NULL.
+  void GetGestureTimes(const base::NativeEvent& native_event,
+                       double* start_time,
+                       double* end_time);
+
+  // Normalize the data value on deviceid to fall into [0, 1].
+  // *value = (*value - min_value_of_tp) / (max_value_of_tp - min_value_of_tp)
+  // Returns true and sets the normalized value in|value| if normalization is
+  // successful. Returns false and |value| is unchanged otherwise.
+  bool NormalizeData(unsigned int deviceid,
+                     const DataType type,
+                     double* value);
+
+  // Extract the range of the data type. Return true if the range is available
+  // and written into min & max, false if the range is not available.
+  bool GetDataRange(unsigned int deviceid,
+                    const DataType type,
+                    double* min,
+                    double* max);
+
+  // Sets up relevant valuator informations for device ids in the device lists.
+  // This function is only for test purpose. It does not query the X server for
+  // the actual device info, but rather inits the relevant valuator structures
+  // to have safe default values for testing.
+  void SetDeviceListForTest(const std::vector<unsigned int>& touchscreen,
+                            const std::vector<unsigned int>& cmt_devices);
+
+  void SetValuatorDataForTest(XIDeviceEvent* xievent,
+                              DataType type,
+                              double value);
+
+  bool TouchEventNeedsCalibrate(int touch_device_id) const;
+
+  // Sets the keys which are still allowed on a disabled keyboard device.
+  void SetDisabledKeyboardAllowedKeys(
+      scoped_ptr<std::set<KeyboardCode> > excepted_keys);
+
+  // Disables and enables events from devices by device id.
+  void DisableDevice(unsigned int deviceid);
+  void EnableDevice(unsigned int deviceid);
+
+  // Returns true if |native_event| should be blocked.
+  bool IsEventBlocked(const base::NativeEvent& native_event);
+
+ private:
+  DeviceDataManagerX11();
+  virtual ~DeviceDataManagerX11();
+
+  // Initialize the XInput related system information.
+  bool InitializeXInputInternal();
+
+  // Check if an XI event contains data of the specified type.
+  bool HasEventData(const XIDeviceEvent* xiev, const DataType type) const;
+
+  void InitializeValuatorsForTest(int deviceid,
+                                  int start_valuator,
+                                  int end_valuator,
+                                  double min_value,
+                                  double max_value);
+
+  static const int kMaxXIEventType = XI_LASTEVENT + 1;
+  static const int kMaxSlotNum = 10;
+
+  // Major opcode for the XInput extension. Used to identify XInput events.
+  int xi_opcode_;
+
+  // A quick lookup table for determining if the XI event is an XIDeviceEvent.
+  std::bitset<kMaxXIEventType> xi_device_event_types_;
+
+  // A quick lookup table for determining if events from the pointer device
+  // should be processed.
+  std::bitset<kMaxDeviceNum> cmt_devices_;
+  std::bitset<kMaxDeviceNum> touchpads_;
+
+  // A quick lookup table for determining if events from the XI device
+  // should be blocked.
+  std::bitset<kMaxDeviceNum> blocked_devices_;
+
+  // The set of keys allowed while the keyboard is blocked.
+  scoped_ptr<std::set<KeyboardCode> > blocked_keyboard_allowed_keys_;
+
+  // Number of valuators on the specific device.
+  int valuator_count_[kMaxDeviceNum];
+
+  // Index table to find the valuator for DataType on the specific device
+  // by valuator_lookup_[device_id][data_type].
+  std::vector<int> valuator_lookup_[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];
+
+  // Index table to find the min & max value of the Valuator on a specific
+  // device.
+  std::vector<double> valuator_min_[kMaxDeviceNum];
+  std::vector<double> valuator_max_[kMaxDeviceNum];
+
+  // Table to keep track of the last seen value for the specified valuator for
+  // a specified slot of a device. Defaults to 0 if the valuator for that slot
+  // was not specified in an earlier event. With MT-B/XI2.2, valuators in an
+  // XEvent are not reported if the values haven't changed from the previous
+  // event. So it is necessary to remember these valuators so that chrome
+  // doesn't think X/device doesn't know about the valuators. We currently
+  // use this only on touchscreen devices.
+  std::vector<double> last_seen_valuator_[kMaxDeviceNum][kMaxSlotNum];
+
+  // X11 atoms cache.
+  X11AtomCache atom_cache_;
+
+  unsigned char button_map_[256];
+  int button_map_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceDataManagerX11);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_DEVICE_DATA_MANAGER_X11_H_
diff --git a/ui/events/x/device_list_cache_x.cc b/ui/events/x/device_list_cache_x.cc
new file mode 100644
index 0000000..df930d1
--- /dev/null
+++ b/ui/events/x/device_list_cache_x.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2012 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 "ui/events/x/device_list_cache_x.h"
+
+#include <algorithm>
+
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "ui/events/x/device_data_manager_x11.h"
+
+namespace {
+
+bool IsXI2Available() {
+#if defined(USE_AURA)
+  return ui::DeviceDataManagerX11::GetInstance()->IsXInput2Available();
+#else
+  return false;
+#endif
+}
+
+}
+
+namespace ui {
+
+DeviceListCacheX::DeviceListCacheX() {
+}
+
+DeviceListCacheX::~DeviceListCacheX() {
+  std::map<Display*, XDeviceList>::iterator xp;
+  for (xp = x_dev_list_map_.begin(); xp != x_dev_list_map_.end(); xp++) {
+    if (xp->second.devices)
+      XFreeDeviceList(xp->second.devices);
+  }
+  std::map<Display*, XIDeviceList>::iterator xip;
+  for (xip = xi_dev_list_map_.begin(); xip != xi_dev_list_map_.end(); xip++) {
+    if (xip->second.devices)
+      XIFreeDeviceInfo(xip->second.devices);
+  }
+}
+
+DeviceListCacheX* DeviceListCacheX::GetInstance() {
+  return Singleton<DeviceListCacheX>::get();
+}
+
+void DeviceListCacheX::UpdateDeviceList(Display* display) {
+  XDeviceList& new_x_dev_list = x_dev_list_map_[display];
+  if (new_x_dev_list.devices)
+    XFreeDeviceList(new_x_dev_list.devices);
+  new_x_dev_list.devices = XListInputDevices(display, &new_x_dev_list.count);
+
+  XIDeviceList& new_xi_dev_list = xi_dev_list_map_[display];
+  if (new_xi_dev_list.devices)
+    XIFreeDeviceInfo(new_xi_dev_list.devices);
+  new_xi_dev_list.devices = IsXI2Available() ?
+      XIQueryDevice(display, XIAllDevices, &new_xi_dev_list.count) : NULL;
+}
+
+const XDeviceList& DeviceListCacheX::GetXDeviceList(Display* display) {
+  XDeviceList& x_dev_list = x_dev_list_map_[display];
+  // Note that the function can be called before any update has taken place.
+  if (!x_dev_list.devices && !x_dev_list.count)
+    x_dev_list.devices = XListInputDevices(display, &x_dev_list.count);
+  return x_dev_list;
+}
+
+const XIDeviceList& DeviceListCacheX::GetXI2DeviceList(Display* display) {
+  XIDeviceList& xi_dev_list = xi_dev_list_map_[display];
+  if (!xi_dev_list.devices && !xi_dev_list.count) {
+    xi_dev_list.devices = XIQueryDevice(display, XIAllDevices,
+                                       &xi_dev_list.count);
+  }
+  return xi_dev_list;
+}
+
+}  // namespace ui
+
diff --git a/ui/events/x/device_list_cache_x.h b/ui/events/x/device_list_cache_x.h
new file mode 100644
index 0000000..5456a43
--- /dev/null
+++ b/ui/events/x/device_list_cache_x.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 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 UI_EVENTS_X_DEVICE_LIST_CACHE_X_H_
+#define UI_EVENTS_X_DEVICE_LIST_CACHE_X_H_
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "ui/events/events_base_export.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+typedef struct _XDisplay Display;
+
+template <typename T>
+struct DeviceList {
+  DeviceList() : devices(NULL), count(0) {
+  }
+  T& operator[] (int x) {
+    return devices[x];
+  }
+  const T& operator[](int x) const { return devices[x]; }
+  T* devices;
+  int count;
+};
+
+typedef struct DeviceList<XDeviceInfo> XDeviceList;
+typedef struct DeviceList<XIDeviceInfo> XIDeviceList;
+
+namespace ui {
+
+// A class to cache the current XInput device list. This minimized the
+// round-trip time to the X server whenever such a device list is needed. The
+// update function will be called on each incoming XI_HierarchyChanged event.
+class EVENTS_BASE_EXPORT DeviceListCacheX {
+ public:
+  static DeviceListCacheX* GetInstance();
+
+  void UpdateDeviceList(Display* display);
+
+  // Returns the list of devices associated with |display|. Uses the old X11
+  // protocol to get the list of the devices.
+  const XDeviceList& GetXDeviceList(Display* display);
+
+  // Returns the list of devices associated with |display|. Uses the newer
+  // XINPUT2 protocol to get the list of devices. Before making this call, make
+  // sure that XInput2 support is available (e.g. by calling
+  // IsXInput2Available()).
+  const XIDeviceList& GetXI2DeviceList(Display* display);
+
+ private:
+  friend struct DefaultSingletonTraits<DeviceListCacheX>;
+
+  DeviceListCacheX();
+  ~DeviceListCacheX();
+
+  std::map<Display*, XDeviceList> x_dev_list_map_;
+  std::map<Display*, XIDeviceList> xi_dev_list_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceListCacheX);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_DEVICE_LIST_CACHE_X_H_
+
diff --git a/ui/events/x/events_x.cc b/ui/events/x/events_x.cc
new file mode 100644
index 0000000..371c8f5
--- /dev/null
+++ b/ui/events/x/events_x.cc
@@ -0,0 +1,929 @@
+// Copyright (c) 2012 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 "ui/events/event_constants.h"
+
+#include <cmath>
+#include <string.h>
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "ui/events/event.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/events/x/device_list_cache_x.h"
+#include "ui/events/x/touch_factory_x11.h"
+#include "ui/gfx/display.h"
+#include "ui/gfx/point.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/screen.h"
+#include "ui/gfx/x/x11_atom_cache.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace {
+
+// Scroll amount for each wheelscroll event. 53 is also the value used for GTK+.
+const int kWheelScrollAmount = 53;
+
+const int kMinWheelButton = 4;
+const int kMaxWheelButton = 7;
+
+// A class to track current modifier state on master device. Only track ctrl,
+// alt, shift and caps lock keys currently. The tracked state can then be used
+// by floating device.
+class XModifierStateWatcher{
+ public:
+  static XModifierStateWatcher* GetInstance() {
+    return Singleton<XModifierStateWatcher>::get();
+  }
+
+  int StateFromKeyboardCode(ui::KeyboardCode keyboard_code) {
+    switch (keyboard_code) {
+      case ui::VKEY_CONTROL:
+        return ControlMask;
+      case ui::VKEY_SHIFT:
+        return ShiftMask;
+      case ui::VKEY_MENU:
+        return Mod1Mask;
+      case ui::VKEY_CAPITAL:
+        return LockMask;
+      default:
+        return 0;
+    }
+  }
+
+  void UpdateStateFromXEvent(const base::NativeEvent& native_event) {
+    ui::KeyboardCode keyboard_code = ui::KeyboardCodeFromNative(native_event);
+    unsigned int mask = StateFromKeyboardCode(keyboard_code);
+    // Floating device can't access the modifer state from master device.
+    // We need to track the states of modifier keys in a singleton for
+    // floating devices such as touch screen. Issue 106426 is one example
+    // of why we need the modifier states for floating device.
+    switch (native_event->type) {
+      case KeyPress:
+        state_ = native_event->xkey.state | mask;
+        break;
+      case KeyRelease:
+        state_ = native_event->xkey.state & ~mask;
+        break;
+      case GenericEvent: {
+        XIDeviceEvent* xievent =
+            static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+        switch (xievent->evtype) {
+          case XI_KeyPress:
+            state_ = xievent->mods.effective |= mask;
+            break;
+          case XI_KeyRelease:
+            state_ = xievent->mods.effective &= ~mask;
+            break;
+          default:
+            NOTREACHED();
+            break;
+        }
+        break;
+      }
+      default:
+        NOTREACHED();
+        break;
+    }
+  }
+
+  // Returns the current modifer state in master device. It only contains the
+  // state of ctrl, shift, alt and caps lock keys.
+  unsigned int state() { return state_; }
+
+ private:
+  friend struct DefaultSingletonTraits<XModifierStateWatcher>;
+
+  XModifierStateWatcher() : state_(0) { }
+
+  unsigned int state_;
+
+  DISALLOW_COPY_AND_ASSIGN(XModifierStateWatcher);
+};
+
+#if defined(USE_XI2_MT)
+// Detects if a touch event is a driver-generated 'special event'.
+// A 'special event' is a touch event with maximum radius and pressure at
+// location (0, 0).
+// This needs to be done in a cleaner way: http://crbug.com/169256
+bool TouchEventIsGeneratedHack(const base::NativeEvent& native_event) {
+  XIDeviceEvent* event =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  CHECK(event->evtype == XI_TouchBegin ||
+        event->evtype == XI_TouchUpdate ||
+        event->evtype == XI_TouchEnd);
+
+  // Force is normalized to [0, 1].
+  if (ui::GetTouchForce(native_event) < 1.0f)
+    return false;
+
+  if (ui::EventLocationFromNative(native_event) != gfx::Point())
+    return false;
+
+  // Radius is in pixels, and the valuator is the diameter in pixels.
+  double radius = ui::GetTouchRadiusX(native_event), min, max;
+  unsigned int deviceid =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data)->sourceid;
+  if (!ui::DeviceDataManagerX11::GetInstance()->GetDataRange(
+      deviceid, ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, &min, &max)) {
+    return false;
+  }
+
+  return radius * 2 == max;
+}
+#endif
+
+int GetEventFlagsFromXState(unsigned int state) {
+  int flags = 0;
+  if (state & ControlMask)
+    flags |= ui::EF_CONTROL_DOWN;
+  if (state & ShiftMask)
+    flags |= ui::EF_SHIFT_DOWN;
+  if (state & Mod1Mask)
+    flags |= ui::EF_ALT_DOWN;
+  if (state & LockMask)
+    flags |= ui::EF_CAPS_LOCK_DOWN;
+  if (state & Mod3Mask)
+    flags |= ui::EF_MOD3_DOWN;
+  if (state & Mod4Mask)
+    flags |= ui::EF_COMMAND_DOWN;
+  if (state & Mod5Mask)
+    flags |= ui::EF_ALTGR_DOWN;
+  if (state & Button1Mask)
+    flags |= ui::EF_LEFT_MOUSE_BUTTON;
+  if (state & Button2Mask)
+    flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
+  if (state & Button3Mask)
+    flags |= ui::EF_RIGHT_MOUSE_BUTTON;
+  return flags;
+}
+
+int GetEventFlagsFromXKeyEvent(XEvent* xevent) {
+  DCHECK(xevent->type == KeyPress || xevent->type == KeyRelease);
+
+#if defined(OS_CHROMEOS)
+  const int ime_fabricated_flag = 0;
+#else
+  // XIM fabricates key events for the character compositions by XK_Multi_key.
+  // For example, when a user hits XK_Multi_key, XK_apostrophe, and XK_e in
+  // order to input "é", then XIM generates a key event with keycode=0 and
+  // state=0 for the composition, and the sequence of X11 key events will be
+  // XK_Multi_key, XK_apostrophe, **NoSymbol**, and XK_e.  If the user used
+  // shift key and/or caps lock key, state can be ShiftMask, LockMask or both.
+  //
+  // We have to send these fabricated key events to XIM so it can correctly
+  // handle the character compositions.
+  const unsigned int shift_lock_mask = ShiftMask | LockMask;
+  const bool fabricated_by_xim =
+      xevent->xkey.keycode == 0 &&
+      (xevent->xkey.state & ~shift_lock_mask) == 0;
+  const int ime_fabricated_flag =
+      fabricated_by_xim ? ui::EF_IME_FABRICATED_KEY : 0;
+#endif
+
+  return GetEventFlagsFromXState(xevent->xkey.state) |
+      (xevent->xkey.send_event ? ui::EF_FINAL : 0) |
+      (IsKeypadKey(XLookupKeysym(&xevent->xkey, 0)) ? ui::EF_NUMPAD_KEY : 0) |
+      (IsFunctionKey(XLookupKeysym(&xevent->xkey, 0)) ?
+          ui::EF_FUNCTION_KEY : 0) |
+      ime_fabricated_flag;
+}
+
+int GetEventFlagsFromXGenericEvent(XEvent* xevent) {
+  DCHECK(xevent->type == GenericEvent);
+  XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xevent->xcookie.data);
+  DCHECK((xievent->evtype == XI_KeyPress) ||
+         (xievent->evtype == XI_KeyRelease));
+  return GetEventFlagsFromXState(xievent->mods.effective) |
+         (xevent->xkey.send_event ? ui::EF_FINAL : 0) |
+         (IsKeypadKey(
+              XkbKeycodeToKeysym(xievent->display, xievent->detail, 0, 0))
+              ? ui::EF_NUMPAD_KEY
+              : 0);
+}
+
+// Get the event flag for the button in XButtonEvent. During a ButtonPress
+// event, |state| in XButtonEvent does not include the button that has just been
+// pressed. Instead |state| contains flags for the buttons (if any) that had
+// already been pressed before the current button, and |button| stores the most
+// current pressed button. So, if you press down left mouse button, and while
+// pressing it down, press down the right mouse button, then for the latter
+// event, |state| would have Button1Mask set but not Button3Mask, and |button|
+// would be 3.
+int GetEventFlagsForButton(int button) {
+  switch (button) {
+    case 1:
+      return ui::EF_LEFT_MOUSE_BUTTON;
+    case 2:
+      return ui::EF_MIDDLE_MOUSE_BUTTON;
+    case 3:
+      return ui::EF_RIGHT_MOUSE_BUTTON;
+    default:
+      return 0;
+  }
+}
+
+int GetButtonMaskForX2Event(XIDeviceEvent* xievent) {
+  int buttonflags = 0;
+  for (int i = 0; i < 8 * xievent->buttons.mask_len; i++) {
+    if (XIMaskIsSet(xievent->buttons.mask, i)) {
+      int button = (xievent->sourceid == xievent->deviceid) ?
+          ui::DeviceDataManagerX11::GetInstance()->GetMappedButton(i) : i;
+      buttonflags |= GetEventFlagsForButton(button);
+    }
+  }
+  return buttonflags;
+}
+
+ui::EventType GetTouchEventType(const base::NativeEvent& native_event) {
+  XIDeviceEvent* event =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+#if defined(USE_XI2_MT)
+  switch(event->evtype) {
+    case XI_TouchBegin:
+      return TouchEventIsGeneratedHack(native_event) ? ui::ET_UNKNOWN :
+                                                       ui::ET_TOUCH_PRESSED;
+    case XI_TouchUpdate:
+      return TouchEventIsGeneratedHack(native_event) ? ui::ET_UNKNOWN :
+                                                       ui::ET_TOUCH_MOVED;
+    case XI_TouchEnd:
+      return TouchEventIsGeneratedHack(native_event) ? ui::ET_TOUCH_CANCELLED :
+                                                       ui::ET_TOUCH_RELEASED;
+  }
+#endif  // defined(USE_XI2_MT)
+
+  DCHECK(ui::TouchFactory::GetInstance()->IsTouchDevice(event->sourceid));
+  switch (event->evtype) {
+    case XI_ButtonPress:
+      return ui::ET_TOUCH_PRESSED;
+    case XI_ButtonRelease:
+      return ui::ET_TOUCH_RELEASED;
+    case XI_Motion:
+      // Should not convert any emulated Motion event from touch device to
+      // touch event.
+      if (!(event->flags & XIPointerEmulated) &&
+          GetButtonMaskForX2Event(event))
+        return ui::ET_TOUCH_MOVED;
+      return ui::ET_UNKNOWN;
+    default:
+      NOTREACHED();
+  }
+  return ui::ET_UNKNOWN;
+}
+
+double GetTouchParamFromXEvent(XEvent* xev,
+                              ui::DeviceDataManagerX11::DataType val,
+                              double default_value) {
+  ui::DeviceDataManagerX11::GetInstance()->GetEventData(
+      *xev, val, &default_value);
+  return default_value;
+}
+
+void ScaleTouchRadius(XEvent* xev, double* radius) {
+  DCHECK_EQ(GenericEvent, xev->type);
+  XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(xev->xcookie.data);
+  ui::DeviceDataManagerX11::GetInstance()->ApplyTouchRadiusScale(
+      xiev->sourceid, radius);
+}
+
+unsigned int UpdateX11EventFlags(int ui_flags, unsigned int old_x_flags) {
+  static struct {
+    int ui;
+    int x;
+  } flags[] = {
+    {ui::EF_CONTROL_DOWN, ControlMask},
+    {ui::EF_SHIFT_DOWN, ShiftMask},
+    {ui::EF_ALT_DOWN, Mod1Mask},
+    {ui::EF_CAPS_LOCK_DOWN, LockMask},
+    {ui::EF_ALTGR_DOWN, Mod5Mask},
+    {ui::EF_COMMAND_DOWN, Mod4Mask},
+    {ui::EF_MOD3_DOWN, Mod3Mask},
+    {ui::EF_NUMPAD_KEY, Mod2Mask},
+    {ui::EF_LEFT_MOUSE_BUTTON, Button1Mask},
+    {ui::EF_MIDDLE_MOUSE_BUTTON, Button2Mask},
+    {ui::EF_RIGHT_MOUSE_BUTTON, Button3Mask},
+  };
+  unsigned int new_x_flags = old_x_flags;
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(flags); ++i) {
+    if (ui_flags & flags[i].ui)
+      new_x_flags |= flags[i].x;
+    else
+      new_x_flags &= ~flags[i].x;
+  }
+  return new_x_flags;
+}
+
+unsigned int UpdateX11EventButton(int ui_flag, unsigned int old_x_button) {
+  switch (ui_flag) {
+    case ui::EF_LEFT_MOUSE_BUTTON:
+      return Button1;
+    case ui::EF_MIDDLE_MOUSE_BUTTON:
+      return Button2;
+    case ui::EF_RIGHT_MOUSE_BUTTON:
+      return Button3;
+    default:
+      return old_x_button;
+  }
+  NOTREACHED();
+}
+
+bool GetGestureTimes(const base::NativeEvent& native_event,
+                     double* start_time,
+                     double* end_time) {
+  if (!ui::DeviceDataManagerX11::GetInstance()->HasGestureTimes(native_event))
+    return false;
+
+  double start_time_, end_time_;
+  if (!start_time)
+    start_time = &start_time_;
+  if (!end_time)
+    end_time = &end_time_;
+
+  ui::DeviceDataManagerX11::GetInstance()->GetGestureTimes(
+      native_event, start_time, end_time);
+  return true;
+}
+
+}  // namespace
+
+namespace ui {
+
+void UpdateDeviceList() {
+  XDisplay* display = gfx::GetXDisplay();
+  DeviceListCacheX::GetInstance()->UpdateDeviceList(display);
+  TouchFactory::GetInstance()->UpdateDeviceList(display);
+  DeviceDataManagerX11::GetInstance()->UpdateDeviceList(display);
+}
+
+EventType EventTypeFromNative(const base::NativeEvent& native_event) {
+  // Allow the DeviceDataManager to block the event. If blocked return
+  // ET_UNKNOWN as the type so this event will not be further processed.
+  // NOTE: During some events unittests there is no device data manager.
+  if (DeviceDataManager::HasInstance() &&
+      static_cast<DeviceDataManagerX11*>(DeviceDataManager::GetInstance())->
+          IsEventBlocked(native_event)) {
+    return ET_UNKNOWN;
+  }
+
+  switch (native_event->type) {
+    case KeyPress:
+      return ET_KEY_PRESSED;
+    case KeyRelease:
+      return ET_KEY_RELEASED;
+    case ButtonPress:
+      if (static_cast<int>(native_event->xbutton.button) >= kMinWheelButton &&
+          static_cast<int>(native_event->xbutton.button) <= kMaxWheelButton)
+        return ET_MOUSEWHEEL;
+      return ET_MOUSE_PRESSED;
+    case ButtonRelease:
+      // Drop wheel events; we should've already scrolled on the press.
+      if (static_cast<int>(native_event->xbutton.button) >= kMinWheelButton &&
+          static_cast<int>(native_event->xbutton.button) <= kMaxWheelButton)
+        return ET_UNKNOWN;
+      return ET_MOUSE_RELEASED;
+    case MotionNotify:
+      if (native_event->xmotion.state &
+          (Button1Mask | Button2Mask | Button3Mask))
+        return ET_MOUSE_DRAGGED;
+      return ET_MOUSE_MOVED;
+    case EnterNotify:
+      // The standard on Windows is to send a MouseMove event when the mouse
+      // first enters a window instead of sending a special mouse enter event.
+      // To be consistent we follow the same style.
+      return ET_MOUSE_MOVED;
+    case LeaveNotify:
+      return ET_MOUSE_EXITED;
+    case GenericEvent: {
+      TouchFactory* factory = TouchFactory::GetInstance();
+      if (!factory->ShouldProcessXI2Event(native_event))
+        return ET_UNKNOWN;
+
+      XIDeviceEvent* xievent =
+          static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+
+      // This check works only for master and floating slave devices. That is
+      // why it is necessary to check for the XI_Touch* events in the following
+      // switch statement to account for attached-slave touchscreens.
+      if (factory->IsTouchDevice(xievent->sourceid))
+        return GetTouchEventType(native_event);
+
+      switch (xievent->evtype) {
+        case XI_TouchBegin:
+          return ui::ET_TOUCH_PRESSED;
+        case XI_TouchUpdate:
+          return ui::ET_TOUCH_MOVED;
+        case XI_TouchEnd:
+          return ui::ET_TOUCH_RELEASED;
+        case XI_ButtonPress: {
+          int button = EventButtonFromNative(native_event);
+          if (button >= kMinWheelButton && button <= kMaxWheelButton)
+            return ET_MOUSEWHEEL;
+          return ET_MOUSE_PRESSED;
+        }
+        case XI_ButtonRelease: {
+          int button = EventButtonFromNative(native_event);
+          // Drop wheel events; we should've already scrolled on the press.
+          if (button >= kMinWheelButton && button <= kMaxWheelButton)
+            return ET_UNKNOWN;
+          return ET_MOUSE_RELEASED;
+        }
+        case XI_Motion: {
+          bool is_cancel;
+          DeviceDataManagerX11* devices = DeviceDataManagerX11::GetInstance();
+          if (GetFlingData(native_event, NULL, NULL, NULL, NULL, &is_cancel))
+            return is_cancel ? ET_SCROLL_FLING_CANCEL : ET_SCROLL_FLING_START;
+          if (devices->IsScrollEvent(native_event)) {
+            return devices->IsTouchpadXInputEvent(native_event) ? ET_SCROLL
+                                                                : ET_MOUSEWHEEL;
+          }
+          if (devices->IsCMTMetricsEvent(native_event))
+            return ET_UMA_DATA;
+          if (GetButtonMaskForX2Event(xievent))
+            return ET_MOUSE_DRAGGED;
+          return ET_MOUSE_MOVED;
+        }
+        case XI_KeyPress:
+          return ET_KEY_PRESSED;
+        case XI_KeyRelease:
+          return ET_KEY_RELEASED;
+      }
+    }
+    default:
+      break;
+  }
+  return ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(const base::NativeEvent& native_event) {
+  switch (native_event->type) {
+    case KeyPress:
+    case KeyRelease: {
+      XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(native_event);
+      return GetEventFlagsFromXKeyEvent(native_event);
+    }
+    case ButtonPress:
+    case ButtonRelease: {
+      int flags = GetEventFlagsFromXState(native_event->xbutton.state);
+      const EventType type = EventTypeFromNative(native_event);
+      if (type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED)
+        flags |= GetEventFlagsForButton(native_event->xbutton.button);
+      return flags;
+    }
+    case EnterNotify:
+    case LeaveNotify:
+      return GetEventFlagsFromXState(native_event->xcrossing.state);
+    case MotionNotify:
+      return GetEventFlagsFromXState(native_event->xmotion.state);
+    case GenericEvent: {
+      XIDeviceEvent* xievent =
+          static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+
+      switch (xievent->evtype) {
+#if defined(USE_XI2_MT)
+        case XI_TouchBegin:
+        case XI_TouchUpdate:
+        case XI_TouchEnd:
+          return GetButtonMaskForX2Event(xievent) |
+                 GetEventFlagsFromXState(xievent->mods.effective) |
+                 GetEventFlagsFromXState(
+                     XModifierStateWatcher::GetInstance()->state());
+          break;
+#endif
+        case XI_ButtonPress:
+        case XI_ButtonRelease: {
+          const bool touch =
+              TouchFactory::GetInstance()->IsTouchDevice(xievent->sourceid);
+          int flags = GetButtonMaskForX2Event(xievent) |
+                      GetEventFlagsFromXState(xievent->mods.effective);
+          if (touch) {
+            flags |= GetEventFlagsFromXState(
+                XModifierStateWatcher::GetInstance()->state());
+          }
+
+          const EventType type = EventTypeFromNative(native_event);
+          int button = EventButtonFromNative(native_event);
+          if ((type == ET_MOUSE_PRESSED || type == ET_MOUSE_RELEASED) && !touch)
+            flags |= GetEventFlagsForButton(button);
+          return flags;
+        }
+        case XI_Motion:
+          return GetButtonMaskForX2Event(xievent) |
+                 GetEventFlagsFromXState(xievent->mods.effective);
+        case XI_KeyPress:
+        case XI_KeyRelease: {
+          XModifierStateWatcher::GetInstance()->UpdateStateFromXEvent(
+              native_event);
+          return GetEventFlagsFromXGenericEvent(native_event);
+        }
+      }
+    }
+  }
+  return 0;
+}
+
+base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
+  switch(native_event->type) {
+    case KeyPress:
+    case KeyRelease:
+      return base::TimeDelta::FromMilliseconds(native_event->xkey.time);
+    case ButtonPress:
+    case ButtonRelease:
+      return base::TimeDelta::FromMilliseconds(native_event->xbutton.time);
+      break;
+    case MotionNotify:
+      return base::TimeDelta::FromMilliseconds(native_event->xmotion.time);
+      break;
+    case EnterNotify:
+    case LeaveNotify:
+      return base::TimeDelta::FromMilliseconds(native_event->xcrossing.time);
+      break;
+    case GenericEvent: {
+      double start, end;
+      double touch_timestamp;
+      if (GetGestureTimes(native_event, &start, &end)) {
+        // If the driver supports gesture times, use them.
+        return base::TimeDelta::FromMicroseconds(end * 1000000);
+      } else if (DeviceDataManagerX11::GetInstance()->GetEventData(
+          *native_event,
+          DeviceDataManagerX11::DT_TOUCH_RAW_TIMESTAMP,
+          &touch_timestamp)) {
+        return base::TimeDelta::FromMicroseconds(touch_timestamp * 1000000);
+      } else {
+        XIDeviceEvent* xide =
+            static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+        return base::TimeDelta::FromMilliseconds(xide->time);
+      }
+      break;
+    }
+  }
+  NOTREACHED();
+  return base::TimeDelta();
+}
+
+gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
+  switch (native_event->type) {
+    case EnterNotify:
+    case LeaveNotify:
+      return gfx::Point(native_event->xcrossing.x, native_event->xcrossing.y);
+    case ButtonPress:
+    case ButtonRelease:
+      return gfx::Point(native_event->xbutton.x, native_event->xbutton.y);
+    case MotionNotify:
+      return gfx::Point(native_event->xmotion.x, native_event->xmotion.y);
+    case GenericEvent: {
+      XIDeviceEvent* xievent =
+          static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+      float x = xievent->event_x;
+      float y = xievent->event_y;
+#if defined(OS_CHROMEOS)
+      switch (xievent->evtype) {
+        case XI_TouchBegin:
+        case XI_TouchUpdate:
+        case XI_TouchEnd:
+          ui::DeviceDataManagerX11::GetInstance()->ApplyTouchTransformer(
+              xievent->deviceid, &x, &y);
+          break;
+        default:
+          break;
+      }
+#endif  // defined(OS_CHROMEOS)
+      return gfx::Point(static_cast<int>(x), static_cast<int>(y));
+    }
+  }
+  return gfx::Point();
+}
+
+gfx::Point EventSystemLocationFromNative(
+    const base::NativeEvent& native_event) {
+  switch (native_event->type) {
+    case EnterNotify:
+    case LeaveNotify: {
+      return gfx::Point(native_event->xcrossing.x_root,
+                        native_event->xcrossing.y_root);
+    }
+    case ButtonPress:
+    case ButtonRelease: {
+      return gfx::Point(native_event->xbutton.x_root,
+                        native_event->xbutton.y_root);
+    }
+    case MotionNotify: {
+      return gfx::Point(native_event->xmotion.x_root,
+                        native_event->xmotion.y_root);
+    }
+    case GenericEvent: {
+      XIDeviceEvent* xievent =
+          static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+      return gfx::Point(xievent->root_x, xievent->root_y);
+    }
+  }
+
+  return gfx::Point();
+}
+
+int EventButtonFromNative(const base::NativeEvent& native_event) {
+  CHECK_EQ(GenericEvent, native_event->type);
+  XIDeviceEvent* xievent =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+  int button = xievent->detail;
+
+  return (xievent->sourceid == xievent->deviceid) ?
+         DeviceDataManagerX11::GetInstance()->GetMappedButton(button) : button;
+}
+
+KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
+  return KeyboardCodeFromXKeyEvent(native_event);
+}
+
+const char* CodeFromNative(const base::NativeEvent& native_event) {
+  return CodeFromXEvent(native_event);
+}
+
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+  XKeyEvent* xkey = NULL;
+  XEvent xkey_from_xi2;
+  switch (native_event->type) {
+    case KeyPress:
+    case KeyRelease:
+      xkey = &native_event->xkey;
+      break;
+    case GenericEvent: {
+      XIDeviceEvent* xievent =
+          static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+      switch (xievent->evtype) {
+        case XI_KeyPress:
+        case XI_KeyRelease:
+          // Build an XKeyEvent corresponding to the XI2 event,
+          // so that we can call XLookupString on it.
+          InitXKeyEventFromXIDeviceEvent(*native_event, &xkey_from_xi2);
+          xkey = &xkey_from_xi2.xkey;
+          break;
+        default:
+          NOTREACHED();
+          break;
+      }
+      break;
+    }
+    default:
+      NOTREACHED();
+      break;
+  }
+  KeySym keysym = XK_VoidSymbol;
+  if (xkey)
+    XLookupString(xkey, NULL, 0, &keysym, NULL);
+  return keysym;
+}
+
+bool IsCharFromNative(const base::NativeEvent& native_event) {
+  return false;
+}
+
+int GetChangedMouseButtonFlagsFromNative(
+    const base::NativeEvent& native_event) {
+  switch (native_event->type) {
+    case ButtonPress:
+    case ButtonRelease:
+      return GetEventFlagsFromXState(native_event->xbutton.state);
+    case GenericEvent: {
+      XIDeviceEvent* xievent =
+          static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+      switch (xievent->evtype) {
+        case XI_ButtonPress:
+        case XI_ButtonRelease:
+          return GetEventFlagsForButton(EventButtonFromNative(native_event));
+        default:
+          break;
+      }
+    }
+    default:
+      break;
+  }
+  return 0;
+}
+
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
+  float x_offset, y_offset;
+  if (GetScrollOffsets(
+      native_event, &x_offset, &y_offset, NULL, NULL, NULL)) {
+    return gfx::Vector2d(static_cast<int>(x_offset),
+                         static_cast<int>(y_offset));
+  }
+
+  int button = native_event->type == GenericEvent ?
+      EventButtonFromNative(native_event) : native_event->xbutton.button;
+
+  switch (button) {
+    case 4:
+      return gfx::Vector2d(0, kWheelScrollAmount);
+    case 5:
+      return gfx::Vector2d(0, -kWheelScrollAmount);
+    case 6:
+      return gfx::Vector2d(kWheelScrollAmount, 0);
+    case 7:
+      return gfx::Vector2d(-kWheelScrollAmount, 0);
+    default:
+      return gfx::Vector2d();
+  }
+}
+
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+  if (!event || event->type == GenericEvent)
+    return NULL;
+  XEvent* copy = new XEvent;
+  *copy = *event;
+  return copy;
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+  delete event;
+}
+
+void IncrementTouchIdRefCount(const base::NativeEvent& xev) {
+  ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+  double tracking_id;
+  if (!manager->GetEventData(
+          *xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
+    return;
+  }
+
+  ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+  factory->AcquireSlotForTrackingID(tracking_id);
+}
+
+void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
+  ui::EventType type = ui::EventTypeFromNative(xev);
+  if (type == ui::ET_TOUCH_CANCELLED ||
+      type == ui::ET_TOUCH_RELEASED) {
+    ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+    ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+    double tracking_id;
+    if (manager->GetEventData(
+        *xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
+      factory->ReleaseSlotForTrackingID(tracking_id);
+    }
+  }
+}
+
+int GetTouchId(const base::NativeEvent& xev) {
+  double slot = 0;
+  ui::DeviceDataManagerX11* manager = ui::DeviceDataManagerX11::GetInstance();
+  double tracking_id;
+  if (!manager->GetEventData(
+      *xev, ui::DeviceDataManagerX11::DT_TOUCH_TRACKING_ID, &tracking_id)) {
+    LOG(ERROR) << "Could not get the tracking ID for the event. Using 0.";
+  } else {
+    ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+    slot = factory->GetSlotForTrackingID(tracking_id);
+  }
+  return slot;
+}
+
+float GetTouchRadiusX(const base::NativeEvent& native_event) {
+  double radius = GetTouchParamFromXEvent(native_event,
+      ui::DeviceDataManagerX11::DT_TOUCH_MAJOR, 0.0) / 2.0;
+  ScaleTouchRadius(native_event, &radius);
+  return radius;
+}
+
+float GetTouchRadiusY(const base::NativeEvent& native_event) {
+  double radius = GetTouchParamFromXEvent(native_event,
+      ui::DeviceDataManagerX11::DT_TOUCH_MINOR, 0.0) / 2.0;
+  ScaleTouchRadius(native_event, &radius);
+  return radius;
+}
+
+float GetTouchAngle(const base::NativeEvent& native_event) {
+  return GetTouchParamFromXEvent(native_event,
+      ui::DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.0) / 2.0;
+}
+
+float GetTouchForce(const base::NativeEvent& native_event) {
+  double force = 0.0;
+  force = GetTouchParamFromXEvent(native_event,
+      ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, 0.0);
+  unsigned int deviceid =
+      static_cast<XIDeviceEvent*>(native_event->xcookie.data)->sourceid;
+  // Force is normalized to fall into [0, 1]
+  if (!ui::DeviceDataManagerX11::GetInstance()->NormalizeData(
+      deviceid, ui::DeviceDataManagerX11::DT_TOUCH_PRESSURE, &force))
+    force = 0.0;
+  return force;
+}
+
+bool GetScrollOffsets(const base::NativeEvent& native_event,
+                      float* x_offset,
+                      float* y_offset,
+                      float* x_offset_ordinal,
+                      float* y_offset_ordinal,
+                      int* finger_count) {
+  if (!DeviceDataManagerX11::GetInstance()->IsScrollEvent(native_event))
+    return false;
+
+  // 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;
+}
+
+bool GetFlingData(const base::NativeEvent& native_event,
+                  float* vx,
+                  float* vy,
+                  float* vx_ordinal,
+                  float* vy_ordinal,
+                  bool* is_cancel) {
+  if (!DeviceDataManagerX11::GetInstance()->IsFlingEvent(native_event))
+    return false;
+
+  float vx_, vy_;
+  float vx_ordinal_, vy_ordinal_;
+  bool is_cancel_;
+  if (!vx)
+    vx = &vx_;
+  if (!vy)
+    vy = &vy_;
+  if (!vx_ordinal)
+    vx_ordinal = &vx_ordinal_;
+  if (!vy_ordinal)
+    vy_ordinal = &vy_ordinal_;
+  if (!is_cancel)
+    is_cancel = &is_cancel_;
+
+  DeviceDataManagerX11::GetInstance()->GetFlingData(
+      native_event, vx, vy, vx_ordinal, vy_ordinal, is_cancel);
+  return true;
+}
+
+void UpdateX11EventForFlags(Event* event) {
+  XEvent* xev = event->native_event();
+  if (!xev)
+    return;
+  switch (xev->type) {
+    case KeyPress:
+    case KeyRelease:
+      xev->xkey.state = UpdateX11EventFlags(event->flags(), xev->xkey.state);
+      break;
+    case ButtonPress:
+    case ButtonRelease:
+      xev->xbutton.state =
+          UpdateX11EventFlags(event->flags(), xev->xbutton.state);
+      break;
+    case GenericEvent: {
+      XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
+      DCHECK(xievent);
+      xievent->mods.effective =
+          UpdateX11EventFlags(event->flags(), xievent->mods.effective);
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+void UpdateX11EventForChangedButtonFlags(MouseEvent* event) {
+  XEvent* xev = event->native_event();
+  if (!xev)
+    return;
+  switch (xev->type) {
+    case ButtonPress:
+    case ButtonRelease:
+      xev->xbutton.button = UpdateX11EventButton(event->changed_button_flags(),
+                                                 xev->xbutton.button);
+      break;
+    case GenericEvent: {
+      XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xev->xcookie.data);
+      CHECK(xievent && (xievent->evtype == XI_ButtonPress ||
+                        xievent->evtype == XI_ButtonRelease));
+      xievent->detail =
+          UpdateX11EventButton(event->changed_button_flags(), xievent->detail);
+      break;
+    }
+    default:
+      break;
+  }
+}
+
+}  // namespace ui
diff --git a/ui/events/x/events_x_unittest.cc b/ui/events/x/events_x_unittest.cc
new file mode 100644
index 0000000..d8e2c83
--- /dev/null
+++ b/ui/events/x/events_x_unittest.cc
@@ -0,0 +1,642 @@
+// Copyright (c) 2012 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 <cstring>
+#include <set>
+
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+
+// Generically-named #defines from Xlib that conflict with symbols in GTest.
+#undef Bool
+#undef None
+
+#include "base/memory/scoped_ptr.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/event_utils.h"
+#include "ui/events/test/events_test_utils.h"
+#include "ui/events/test/events_test_utils_x11.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/events/x/touch_factory_x11.h"
+#include "ui/gfx/point.h"
+
+namespace ui {
+
+namespace {
+
+// Initializes the passed-in Xlib event.
+void InitButtonEvent(XEvent* event,
+                     bool is_press,
+                     const gfx::Point& location,
+                     int button,
+                     int state) {
+  memset(event, 0, sizeof(*event));
+
+  // We don't bother setting fields that the event code doesn't use, such as
+  // x_root/y_root and window/root/subwindow.
+  XButtonEvent* button_event = &(event->xbutton);
+  button_event->type = is_press ? ButtonPress : ButtonRelease;
+  button_event->x = location.x();
+  button_event->y = location.y();
+  button_event->button = button;
+  button_event->state = state;
+}
+
+// Initializes the passed-in Xlib event.
+void InitKeyEvent(Display* display,
+                  XEvent* event,
+                  bool is_press,
+                  int keycode,
+                  int state) {
+  memset(event, 0, sizeof(*event));
+
+  // We don't bother setting fields that the event code doesn't use, such as
+  // x_root/y_root and window/root/subwindow.
+  XKeyEvent* key_event = &(event->xkey);
+  key_event->display = display;
+  key_event->type = is_press ? KeyPress : KeyRelease;
+  key_event->keycode = keycode;
+  key_event->state = state;
+}
+
+// Returns true if the keysym maps to a KeyEvent with the EF_FUNCTION_KEY
+// flag set, or the keysym maps to a zero key code.
+bool HasFunctionKeyFlagSetIfSupported(Display* display, int x_keysym) {
+  XEvent event;
+  int x_keycode = XKeysymToKeycode(display, x_keysym);
+  // Exclude keysyms for which the server has no corresponding keycode.
+  if (x_keycode) {
+    InitKeyEvent(display, &event, true, x_keycode, 0);
+    ui::KeyEvent ui_key_event(&event);
+    return (ui_key_event.flags() & ui::EF_FUNCTION_KEY);
+  }
+  return true;
+}
+
+}  // namespace
+
+class EventsXTest : public testing::Test {
+ public:
+  EventsXTest() {}
+  virtual ~EventsXTest() {}
+
+  virtual void SetUp() OVERRIDE {
+    DeviceDataManagerX11::CreateInstance();
+    ui::TouchFactory::GetInstance()->ResetForTest();
+  }
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EventsXTest);
+};
+
+TEST_F(EventsXTest, ButtonEvents) {
+  XEvent event;
+  gfx::Point location(5, 10);
+  gfx::Vector2d offset;
+
+  InitButtonEvent(&event, true, location, 1, 0);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+
+  InitButtonEvent(&event, true, location, 2, Button1Mask | ShiftMask);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON |
+                ui::EF_SHIFT_DOWN,
+            ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+
+  InitButtonEvent(&event, false, location, 3, 0);
+  EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+
+  // Scroll up.
+  InitButtonEvent(&event, true, location, 4, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+  offset = ui::GetMouseWheelOffset(&event);
+  EXPECT_GT(offset.y(), 0);
+  EXPECT_EQ(0, offset.x());
+
+  // Scroll down.
+  InitButtonEvent(&event, true, location, 5, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+  offset = ui::GetMouseWheelOffset(&event);
+  EXPECT_LT(offset.y(), 0);
+  EXPECT_EQ(0, offset.x());
+
+  // Scroll left.
+  InitButtonEvent(&event, true, location, 6, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+  offset = ui::GetMouseWheelOffset(&event);
+  EXPECT_EQ(0, offset.y());
+  EXPECT_GT(offset.x(), 0);
+
+  // Scroll right.
+  InitButtonEvent(&event, true, location, 7, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
+  EXPECT_EQ(0, ui::EventFlagsFromNative(&event));
+  EXPECT_EQ(location, ui::EventLocationFromNative(&event));
+  offset = ui::GetMouseWheelOffset(&event);
+  EXPECT_EQ(0, offset.y());
+  EXPECT_LT(offset.x(), 0);
+
+  // TODO(derat): Test XInput code.
+}
+
+TEST_F(EventsXTest, AvoidExtraEventsOnWheelRelease) {
+  XEvent event;
+  gfx::Point location(5, 10);
+
+  InitButtonEvent(&event, true, location, 4, 0);
+  EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
+
+  // We should return ET_UNKNOWN for the release event instead of returning
+  // ET_MOUSEWHEEL; otherwise we'll scroll twice for each scrollwheel step.
+  InitButtonEvent(&event, false, location, 4, 0);
+  EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(&event));
+
+  // TODO(derat): Test XInput code.
+}
+
+TEST_F(EventsXTest, EnterLeaveEvent) {
+  XEvent event;
+  event.xcrossing.type = EnterNotify;
+  event.xcrossing.x = 10;
+  event.xcrossing.y = 20;
+  event.xcrossing.x_root = 110;
+  event.xcrossing.y_root = 120;
+
+  // Mouse enter events are converted to mouse move events to be consistent with
+  // the way views handle mouse enter. See comments for EnterNotify case in
+  // ui::EventTypeFromNative for more details.
+  EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(&event));
+  EXPECT_EQ("10,20", ui::EventLocationFromNative(&event).ToString());
+  EXPECT_EQ("110,120", ui::EventSystemLocationFromNative(&event).ToString());
+
+  event.xcrossing.type = LeaveNotify;
+  event.xcrossing.x = 30;
+  event.xcrossing.y = 40;
+  event.xcrossing.x_root = 230;
+  event.xcrossing.y_root = 240;
+  EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(&event));
+  EXPECT_EQ("30,40", ui::EventLocationFromNative(&event).ToString());
+  EXPECT_EQ("230,240", ui::EventSystemLocationFromNative(&event).ToString());
+}
+
+TEST_F(EventsXTest, ClickCount) {
+  XEvent event;
+  gfx::Point location(5, 10);
+
+  for (int i = 1; i <= 3; ++i) {
+    InitButtonEvent(&event, true, location, 1, 0);
+    {
+      MouseEvent mouseev(&event);
+      EXPECT_EQ(ui::ET_MOUSE_PRESSED, mouseev.type());
+      EXPECT_EQ(i, mouseev.GetClickCount());
+    }
+
+    InitButtonEvent(&event, false, location, 1, 0);
+    {
+      MouseEvent mouseev(&event);
+      EXPECT_EQ(ui::ET_MOUSE_RELEASED, mouseev.type());
+      EXPECT_EQ(i, mouseev.GetClickCount());
+    }
+  }
+}
+
+#if defined(USE_XI2_MT)
+TEST_F(EventsXTest, TouchEventBasic) {
+  std::vector<unsigned int> devices;
+  devices.push_back(0);
+  ui::SetUpTouchDevicesForTest(devices);
+  std::vector<Valuator> valuators;
+
+  // Init touch begin with tracking id 5, touch id 0.
+  valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_MAJOR, 20));
+  valuators.push_back(
+      Valuator(DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.3f));
+  valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_PRESSURE, 100));
+  ui::ScopedXI2Event scoped_xevent;
+  scoped_xevent.InitTouchEvent(
+      0, XI_TouchBegin, 5, gfx::Point(10, 10), valuators);
+  EXPECT_EQ(ui::ET_TOUCH_PRESSED, ui::EventTypeFromNative(scoped_xevent));
+  EXPECT_EQ("10,10", ui::EventLocationFromNative(scoped_xevent).ToString());
+  EXPECT_EQ(GetTouchId(scoped_xevent), 0);
+  EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 10);
+  EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.15f);
+  EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.1f);
+
+  // Touch update, with new orientation info.
+  valuators.clear();
+  valuators.push_back(
+      Valuator(DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.5f));
+  scoped_xevent.InitTouchEvent(
+      0, XI_TouchUpdate, 5, gfx::Point(20, 20), valuators);
+  EXPECT_EQ(ui::ET_TOUCH_MOVED, ui::EventTypeFromNative(scoped_xevent));
+  EXPECT_EQ("20,20", ui::EventLocationFromNative(scoped_xevent).ToString());
+  EXPECT_EQ(GetTouchId(scoped_xevent), 0);
+  EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 10);
+  EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.25f);
+  EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.1f);
+
+  // Another touch with tracking id 6, touch id 1.
+  valuators.clear();
+  valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_MAJOR, 100));
+  valuators.push_back(Valuator(
+      DeviceDataManagerX11::DT_TOUCH_ORIENTATION, 0.9f));
+  valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_PRESSURE, 500));
+  scoped_xevent.InitTouchEvent(
+      0, XI_TouchBegin, 6, gfx::Point(200, 200), valuators);
+  EXPECT_EQ(ui::ET_TOUCH_PRESSED, ui::EventTypeFromNative(scoped_xevent));
+  EXPECT_EQ("200,200", ui::EventLocationFromNative(scoped_xevent).ToString());
+  EXPECT_EQ(GetTouchId(scoped_xevent), 1);
+  EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 50);
+  EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.45f);
+  EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.5f);
+
+  // Touch with tracking id 5 should have old radius/angle value and new pressue
+  // value.
+  valuators.clear();
+  valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_PRESSURE, 50));
+  scoped_xevent.InitTouchEvent(
+      0, XI_TouchEnd, 5, gfx::Point(30, 30), valuators);
+  EXPECT_EQ(ui::ET_TOUCH_RELEASED, ui::EventTypeFromNative(scoped_xevent));
+  EXPECT_EQ("30,30", ui::EventLocationFromNative(scoped_xevent).ToString());
+  EXPECT_EQ(GetTouchId(scoped_xevent), 0);
+  EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 10);
+  EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.25f);
+  EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.05f);
+
+  // Touch with tracking id 6 should have old angle/pressure value and new
+  // radius value.
+  valuators.clear();
+  valuators.push_back(Valuator(DeviceDataManagerX11::DT_TOUCH_MAJOR, 50));
+  scoped_xevent.InitTouchEvent(
+      0, XI_TouchEnd, 6, gfx::Point(200, 200), valuators);
+  EXPECT_EQ(ui::ET_TOUCH_RELEASED, ui::EventTypeFromNative(scoped_xevent));
+  EXPECT_EQ("200,200", ui::EventLocationFromNative(scoped_xevent).ToString());
+  EXPECT_EQ(GetTouchId(scoped_xevent), 1);
+  EXPECT_EQ(GetTouchRadiusX(scoped_xevent), 25);
+  EXPECT_FLOAT_EQ(GetTouchAngle(scoped_xevent), 0.45f);
+  EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.5f);
+}
+
+int GetTouchIdForTrackingId(uint32 tracking_id) {
+  int slot = 0;
+  bool success =
+      TouchFactory::GetInstance()->QuerySlotForTrackingID(tracking_id, &slot);
+  if (success)
+    return slot;
+  return -1;
+}
+
+TEST_F(EventsXTest, TouchEventIdRefcounting) {
+  std::vector<unsigned int> devices;
+  devices.push_back(0);
+  ui::SetUpTouchDevicesForTest(devices);
+  std::vector<Valuator> valuators;
+
+  const int kTrackingId0 = 5;
+  const int kTrackingId1 = 7;
+
+  // Increment ref count once for first touch.
+  ui::ScopedXI2Event xpress0;
+  xpress0.InitTouchEvent(
+      0, XI_TouchBegin, kTrackingId0, gfx::Point(10, 10), valuators);
+  scoped_ptr<ui::TouchEvent> upress0(new ui::TouchEvent(xpress0));
+  EXPECT_EQ(0, GetTouchIdForTrackingId(kTrackingId0));
+
+  // Increment ref count 4 times for second touch.
+  ui::ScopedXI2Event xpress1;
+  xpress1.InitTouchEvent(
+      0, XI_TouchBegin, kTrackingId1, gfx::Point(20, 20), valuators);
+
+  for (int i = 0; i < 4; ++i) {
+    ui::TouchEvent upress1(xpress1);
+    EXPECT_EQ(1, GetTouchIdForTrackingId(kTrackingId1));
+  }
+
+  ui::ScopedXI2Event xrelease1;
+  xrelease1.InitTouchEvent(
+      0, XI_TouchEnd, kTrackingId1, gfx::Point(10, 10), valuators);
+
+  // Decrement ref count 3 times for second touch.
+  for (int i = 0; i < 3; ++i) {
+    ui::TouchEvent urelease1(xrelease1);
+    EXPECT_EQ(1, GetTouchIdForTrackingId(kTrackingId1));
+  }
+
+  // This should clear the touch id of the second touch.
+  scoped_ptr<ui::TouchEvent> urelease1(new ui::TouchEvent(xrelease1));
+  urelease1.reset();
+  EXPECT_EQ(-1, GetTouchIdForTrackingId(kTrackingId1));
+
+  // This should clear the touch id of the first touch.
+  ui::ScopedXI2Event xrelease0;
+  xrelease0.InitTouchEvent(
+      0, XI_TouchEnd, kTrackingId0, gfx::Point(10, 10), valuators);
+  scoped_ptr<ui::TouchEvent> urelease0(new ui::TouchEvent(xrelease0));
+  urelease0.reset();
+  EXPECT_EQ(-1, GetTouchIdForTrackingId(kTrackingId0));
+}
+#endif
+
+TEST_F(EventsXTest, NumpadKeyEvents) {
+  XEvent event;
+  Display* display = gfx::GetXDisplay();
+
+  struct {
+    bool is_numpad_key;
+    int x_keysym;
+  } keys[] = {
+    // XK_KP_Space and XK_KP_Equal are the extrema in the conventional
+    // keysymdef.h numbering.
+    { true,  XK_KP_Space },
+    { true,  XK_KP_Equal },
+    // Other numpad keysyms. (This is actually exhaustive in the current list.)
+    { true,  XK_KP_Tab },
+    { true,  XK_KP_Enter },
+    { true,  XK_KP_F1 },
+    { true,  XK_KP_F2 },
+    { true,  XK_KP_F3 },
+    { true,  XK_KP_F4 },
+    { true,  XK_KP_Home },
+    { true,  XK_KP_Left },
+    { true,  XK_KP_Up },
+    { true,  XK_KP_Right },
+    { true,  XK_KP_Down },
+    { true,  XK_KP_Prior },
+    { true,  XK_KP_Page_Up },
+    { true,  XK_KP_Next },
+    { true,  XK_KP_Page_Down },
+    { true,  XK_KP_End },
+    { true,  XK_KP_Begin },
+    { true,  XK_KP_Insert },
+    { true,  XK_KP_Delete },
+    { true,  XK_KP_Multiply },
+    { true,  XK_KP_Add },
+    { true,  XK_KP_Separator },
+    { true,  XK_KP_Subtract },
+    { true,  XK_KP_Decimal },
+    { true,  XK_KP_Divide },
+    { true,  XK_KP_0 },
+    { true,  XK_KP_1 },
+    { true,  XK_KP_2 },
+    { true,  XK_KP_3 },
+    { true,  XK_KP_4 },
+    { true,  XK_KP_5 },
+    { true,  XK_KP_6 },
+    { true,  XK_KP_7 },
+    { true,  XK_KP_8 },
+    { true,  XK_KP_9 },
+    // Largest keysym preceding XK_KP_Space.
+    { false, XK_Num_Lock },
+    // Smallest keysym following XK_KP_Equal.
+    { false, XK_F1 },
+    // Non-numpad analogues of numpad keysyms.
+    { false, XK_Tab },
+    { false, XK_Return },
+    { false, XK_F1 },
+    { false, XK_F2 },
+    { false, XK_F3 },
+    { false, XK_F4 },
+    { false, XK_Home },
+    { false, XK_Left },
+    { false, XK_Up },
+    { false, XK_Right },
+    { false, XK_Down },
+    { false, XK_Prior },
+    { false, XK_Page_Up },
+    { false, XK_Next },
+    { false, XK_Page_Down },
+    { false, XK_End },
+    { false, XK_Insert },
+    { false, XK_Delete },
+    { false, XK_multiply },
+    { false, XK_plus },
+    { false, XK_minus },
+    { false, XK_period },
+    { false, XK_slash },
+    { false, XK_0 },
+    { false, XK_1 },
+    { false, XK_2 },
+    { false, XK_3 },
+    { false, XK_4 },
+    { false, XK_5 },
+    { false, XK_6 },
+    { false, XK_7 },
+    { false, XK_8 },
+    { false, XK_9 },
+    // Miscellaneous other keysyms.
+    { false, XK_BackSpace },
+    { false, XK_Scroll_Lock },
+    { false, XK_Multi_key },
+    { false, XK_Select },
+    { false, XK_Num_Lock },
+    { false, XK_Shift_L },
+    { false, XK_space },
+    { false, XK_A },
+  };
+
+  for (size_t k = 0; k < ARRAYSIZE_UNSAFE(keys); ++k) {
+    int x_keycode = XKeysymToKeycode(display, keys[k].x_keysym);
+    // Exclude keysyms for which the server has no corresponding keycode.
+    if (x_keycode) {
+      InitKeyEvent(display, &event, true, x_keycode, 0);
+      // int keysym = XLookupKeysym(&event.xkey, 0);
+      // if (keysym) {
+      ui::KeyEvent ui_key_event(&event);
+      EXPECT_EQ(keys[k].is_numpad_key ? ui::EF_NUMPAD_KEY : 0,
+                ui_key_event.flags() & ui::EF_NUMPAD_KEY);
+    }
+  }
+}
+
+TEST_F(EventsXTest, FunctionKeyEvents) {
+  Display* display = gfx::GetXDisplay();
+
+  // Min  function key code minus 1.
+  EXPECT_FALSE(HasFunctionKeyFlagSetIfSupported(display, XK_F1 - 1));
+  // All function keys.
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F1));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F2));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F3));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F4));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F5));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F6));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F7));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F8));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F9));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F10));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F11));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F12));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F13));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F14));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F15));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F16));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F17));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F18));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F19));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F20));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F21));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F22));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F23));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F24));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F25));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F26));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F27));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F28));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F29));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F30));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F31));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F32));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F33));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F34));
+  EXPECT_TRUE(HasFunctionKeyFlagSetIfSupported(display, XK_F35));
+  // Max function key code plus 1.
+  EXPECT_FALSE(HasFunctionKeyFlagSetIfSupported(display, XK_F35 + 1));
+}
+
+#if defined(USE_XI2_MT)
+// Verifies that the type of events from a disabled keyboard is ET_UNKNOWN, but
+// that an exception list of keys can still be processed.
+TEST_F(EventsXTest, DisableKeyboard) {
+  DeviceDataManagerX11* device_data_manager =
+      static_cast<DeviceDataManagerX11*>(
+          DeviceDataManager::GetInstance());
+  unsigned int blocked_device_id = 1;
+  unsigned int other_device_id = 2;
+  unsigned int master_device_id = 3;
+  device_data_manager->DisableDevice(blocked_device_id);
+
+  scoped_ptr<std::set<KeyboardCode> > excepted_keys(new std::set<KeyboardCode>);
+  excepted_keys->insert(VKEY_B);
+  device_data_manager->SetDisabledKeyboardAllowedKeys(excepted_keys.Pass());
+
+  ScopedXI2Event xev;
+  // A is not allowed on the blocked keyboard, and should return ET_UNKNOWN.
+  xev.InitGenericKeyEvent(master_device_id,
+                          blocked_device_id,
+                          ui::ET_KEY_PRESSED,
+                          ui::VKEY_A,
+                          0);
+  EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(xev));
+
+  // The B key is allowed as an exception, and should return KEY_PRESSED.
+  xev.InitGenericKeyEvent(master_device_id,
+                          blocked_device_id,
+                          ui::ET_KEY_PRESSED,
+                          ui::VKEY_B,
+                          0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+
+  // Both A and B are allowed on an unblocked keyboard device.
+  xev.InitGenericKeyEvent(master_device_id,
+                          other_device_id,
+                          ui::ET_KEY_PRESSED,
+                          ui::VKEY_A,
+                          0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+  xev.InitGenericKeyEvent(master_device_id,
+                          other_device_id,
+                          ui::ET_KEY_PRESSED,
+                          ui::VKEY_B,
+                          0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+
+  device_data_manager->EnableDevice(blocked_device_id);
+  device_data_manager->SetDisabledKeyboardAllowedKeys(
+      scoped_ptr<std::set<KeyboardCode> >());
+
+  // A key returns KEY_PRESSED as per usual now that keyboard was re-enabled.
+  xev.InitGenericKeyEvent(master_device_id,
+                          blocked_device_id,
+                          ui::ET_KEY_PRESSED,
+                          ui::VKEY_A,
+                          0);
+  EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(xev));
+}
+
+// Verifies that the type of events from a disabled mouse is ET_UNKNOWN.
+TEST_F(EventsXTest, DisableMouse) {
+  DeviceDataManagerX11* device_data_manager =
+      static_cast<DeviceDataManagerX11*>(
+          DeviceDataManager::GetInstance());
+  unsigned int blocked_device_id = 1;
+  unsigned int other_device_id = 2;
+  std::vector<unsigned int> device_list;
+  device_list.push_back(blocked_device_id);
+  device_list.push_back(other_device_id);
+  TouchFactory::GetInstance()->SetPointerDeviceForTest(device_list);
+
+  device_data_manager->DisableDevice(blocked_device_id);
+
+  ScopedXI2Event xev;
+  xev.InitGenericButtonEvent(blocked_device_id, ET_MOUSE_PRESSED, gfx::Point(),
+      EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(ui::ET_UNKNOWN, ui::EventTypeFromNative(xev));
+
+  xev.InitGenericButtonEvent(other_device_id, ET_MOUSE_PRESSED, gfx::Point(),
+      EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(xev));
+
+  device_data_manager->EnableDevice(blocked_device_id);
+
+  xev.InitGenericButtonEvent(blocked_device_id, ET_MOUSE_PRESSED, gfx::Point(),
+      EF_LEFT_MOUSE_BUTTON);
+  EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(xev));
+}
+#endif  // defined(USE_XI2_MT)
+
+#if !defined(OS_CHROMEOS)
+TEST_F(EventsXTest, ImeFabricatedKeyEvents) {
+  Display* display = gfx::GetXDisplay();
+
+  unsigned int state_to_be_fabricated[] = {
+    0, ShiftMask, LockMask, ShiftMask | LockMask,
+  };
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(state_to_be_fabricated); ++i) {
+    unsigned int state = state_to_be_fabricated[i];
+    for (int is_char = 0; is_char < 2; ++is_char) {
+      XEvent x_event;
+      InitKeyEvent(display, &x_event, true, 0, state);
+      ui::KeyEvent key_event(&x_event);
+      if (is_char) {
+        KeyEventTestApi test_event(&key_event);
+        test_event.set_is_char(true);
+      }
+      EXPECT_TRUE(key_event.flags() & ui::EF_IME_FABRICATED_KEY);
+    }
+  }
+
+  unsigned int state_to_be_not_fabricated[] = {
+    ControlMask, Mod1Mask, Mod2Mask, ShiftMask | ControlMask,
+  };
+  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(state_to_be_not_fabricated); ++i) {
+    unsigned int state = state_to_be_not_fabricated[i];
+    for (int is_char = 0; is_char < 2; ++is_char) {
+      XEvent x_event;
+      InitKeyEvent(display, &x_event, true, 0, state);
+      ui::KeyEvent key_event(&x_event);
+      if (is_char) {
+        KeyEventTestApi test_event(&key_event);
+        test_event.set_is_char(true);
+      }
+      EXPECT_FALSE(key_event.flags() & ui::EF_IME_FABRICATED_KEY);
+    }
+  }
+}
+#endif
+
+}  // namespace ui
diff --git a/ui/events/x/hotplug_event_handler_x11.cc b/ui/events/x/hotplug_event_handler_x11.cc
new file mode 100644
index 0000000..16f42f7
--- /dev/null
+++ b/ui/events/x/hotplug_event_handler_x11.cc
@@ -0,0 +1,182 @@
+// Copyright 2014 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 "ui/events/x/hotplug_event_handler_x11.h"
+
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+
+#include <cmath>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/file_enumerator.h"
+#include "base/logging.h"
+#include "base/process/launch.h"
+#include "base/strings/string_util.h"
+#include "base/sys_info.h"
+#include "ui/events/device_hotplug_event_observer.h"
+#include "ui/events/touchscreen_device.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace ui {
+
+namespace {
+
+// We consider the touchscreen to be internal if it is an I2c device.
+// With the device id, we can query X to get the device's dev input
+// node eventXXX. Then we search all the dev input nodes registered
+// by I2C devices to see if we can find eventXXX.
+bool IsTouchscreenInternal(XDisplay* dpy, int device_id) {
+  using base::FileEnumerator;
+  using base::FilePath;
+
+#if !defined(CHROMEOS)
+  return false;
+#else
+  if (!base::SysInfo::IsRunningOnChromeOS())
+    return false;
+#endif
+
+  // Input device has a property "Device Node" pointing to its dev input node,
+  // e.g.   Device Node (250): "/dev/input/event8"
+  Atom device_node = XInternAtom(dpy, "Device Node", False);
+  if (device_node == None)
+    return false;
+
+  Atom actual_type;
+  int actual_format;
+  unsigned long nitems, bytes_after;
+  unsigned char* data;
+  XDevice* dev = XOpenDevice(dpy, device_id);
+  if (!dev)
+    return false;
+
+  if (XGetDeviceProperty(dpy,
+                         dev,
+                         device_node,
+                         0,
+                         1000,
+                         False,
+                         AnyPropertyType,
+                         &actual_type,
+                         &actual_format,
+                         &nitems,
+                         &bytes_after,
+                         &data) != Success) {
+    XCloseDevice(dpy, dev);
+    return false;
+  }
+  base::FilePath dev_node_path(reinterpret_cast<char*>(data));
+  XFree(data);
+  XCloseDevice(dpy, dev);
+
+  std::string event_node = dev_node_path.BaseName().value();
+  if (event_node.empty() || !StartsWithASCII(event_node, "event", false))
+    return false;
+
+  // Extract id "XXX" from "eventXXX"
+  std::string event_node_id = event_node.substr(5);
+
+  // I2C input device registers its dev input node at
+  // /sys/bus/i2c/devices/*/input/inputXXX/eventXXX
+  FileEnumerator i2c_enum(FilePath(FILE_PATH_LITERAL("/sys/bus/i2c/devices/")),
+                          false,
+                          base::FileEnumerator::DIRECTORIES);
+  for (FilePath i2c_name = i2c_enum.Next(); !i2c_name.empty();
+       i2c_name = i2c_enum.Next()) {
+    FileEnumerator input_enum(i2c_name.Append(FILE_PATH_LITERAL("input")),
+                              false,
+                              base::FileEnumerator::DIRECTORIES,
+                              FILE_PATH_LITERAL("input*"));
+    for (base::FilePath input = input_enum.Next(); !input.empty();
+         input = input_enum.Next()) {
+      if (input.BaseName().value().substr(5) == event_node_id)
+        return true;
+    }
+  }
+
+  return false;
+}
+
+}  // namespace
+
+HotplugEventHandlerX11::HotplugEventHandlerX11(
+    DeviceHotplugEventObserver* delegate)
+    : delegate_(delegate) {
+}
+
+HotplugEventHandlerX11::~HotplugEventHandlerX11() {
+}
+
+void HotplugEventHandlerX11::OnHotplugEvent() {
+  const XIDeviceList& device_list =
+      DeviceListCacheX::GetInstance()->GetXI2DeviceList(gfx::GetXDisplay());
+  HandleTouchscreenDevices(device_list);
+}
+
+void HotplugEventHandlerX11::HandleTouchscreenDevices(
+    const XIDeviceList& x11_devices) {
+  std::vector<TouchscreenDevice> devices;
+  Display* display = gfx::GetXDisplay();
+  Atom valuator_x = XInternAtom(display, "Abs MT Position X", False);
+  Atom valuator_y = XInternAtom(display, "Abs MT Position Y", False);
+  if (valuator_x == None || valuator_y == None)
+    return;
+
+  std::set<int> no_match_touchscreen;
+  for (int i = 0; i < x11_devices.count; i++) {
+    if (!x11_devices[i].enabled || x11_devices[i].use != XIFloatingSlave)
+      continue;  // Assume all touchscreens are floating slaves
+
+    double width = -1.0;
+    double height = -1.0;
+    bool is_direct_touch = false;
+
+    for (int j = 0; j < x11_devices[i].num_classes; j++) {
+      XIAnyClassInfo* class_info = x11_devices[i].classes[j];
+
+      if (class_info->type == XIValuatorClass) {
+        XIValuatorClassInfo* valuator_info =
+            reinterpret_cast<XIValuatorClassInfo*>(class_info);
+
+        if (valuator_x == valuator_info->label) {
+          // Ignore X axis valuator with unexpected properties
+          if (valuator_info->number == 0 && valuator_info->mode == Absolute &&
+              valuator_info->min == 0.0) {
+            width = valuator_info->max;
+          }
+        } else if (valuator_y == valuator_info->label) {
+          // Ignore Y axis valuator with unexpected properties
+          if (valuator_info->number == 1 && valuator_info->mode == Absolute &&
+              valuator_info->min == 0.0) {
+            height = valuator_info->max;
+          }
+        }
+      }
+#if defined(USE_XI2_MT)
+      if (class_info->type == XITouchClass) {
+        XITouchClassInfo* touch_info =
+            reinterpret_cast<XITouchClassInfo*>(class_info);
+        is_direct_touch = touch_info->mode == XIDirectTouch;
+      }
+#endif
+    }
+
+    // Touchscreens should have absolute X and Y axes, and be direct touch
+    // devices.
+    if (width > 0.0 && height > 0.0 && is_direct_touch) {
+      bool is_internal =
+          IsTouchscreenInternal(display, x11_devices[i].deviceid);
+      devices.push_back(TouchscreenDevice(
+          x11_devices[i].deviceid, gfx::Size(width, height), is_internal));
+    }
+  }
+
+  delegate_->OnTouchscreenDevicesUpdated(devices);
+}
+
+}  // namespace ui
diff --git a/ui/events/x/hotplug_event_handler_x11.h b/ui/events/x/hotplug_event_handler_x11.h
new file mode 100644
index 0000000..c99fbf2
--- /dev/null
+++ b/ui/events/x/hotplug_event_handler_x11.h
@@ -0,0 +1,34 @@
+// Copyright 2014 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 UI_EVENTS_X_HOTPLUG_EVENT_HANDLER_H_
+#define UI_EVENTS_X_HOTPLUG_EVENT_HANDLER_H_
+
+#include "ui/events/x/device_list_cache_x.h"
+
+namespace ui {
+
+class DeviceHotplugEventObserver;
+
+// Parses X11 native devices and propagates the list of active devices to an
+// observer.
+class EVENTS_BASE_EXPORT HotplugEventHandlerX11 {
+ public:
+  explicit HotplugEventHandlerX11(DeviceHotplugEventObserver* delegate);
+  ~HotplugEventHandlerX11();
+
+  // Called on an X11 hotplug event.
+  void OnHotplugEvent();
+
+ private:
+  void HandleTouchscreenDevices(const XIDeviceList& device_list);
+
+  DeviceHotplugEventObserver* delegate_;  // Not owned.
+
+  DISALLOW_COPY_AND_ASSIGN(HotplugEventHandlerX11);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_HOTPLUG_EVENT_HANDLER_H_
diff --git a/ui/events/x/keysym_to_unicode.cc b/ui/events/x/keysym_to_unicode.cc
new file mode 100644
index 0000000..9e87bff
--- /dev/null
+++ b/ui/events/x/keysym_to_unicode.cc
@@ -0,0 +1,849 @@
+// Copyright 2014 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 "ui/events/x/keysym_to_unicode.h"
+
+// Define XK_xxx before the #include of <X11/keysym.h> so that <X11/keysym.h>
+// defines all KeySyms we need.
+#define XK_MISCELLANY
+#define XK_LATIN1
+#define XK_LATIN2
+#define XK_LATIN3
+#define XK_LATIN4
+#define XK_LATIN8
+#define XK_LATIN9
+#define XK_KATAKANA
+#define XK_ARABIC
+#define XK_CYRILLIC
+#define XK_GREEK
+#define XK_TECHNICAL
+#define XK_SPECIAL
+#define XK_PUBLISHING
+#define XK_APL
+#define XK_HEBREW
+#define XK_THAI
+#define XK_KOREAN
+#define XK_ARMENIAN
+#define XK_GEORGIAN
+#define XK_CAUCASUS
+#define XK_VIETNAMESE
+#define XK_CURRENCY
+#define XK_MATHEMATICAL
+#define XK_BRAILLE
+#define XK_SINHALA
+#include <X11/X.h>
+#include <X11/keysym.h>
+
+#include <unordered_map>
+
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+
+namespace ui {
+
+const struct {
+  KeySym keysym;
+  uint16_t unicode;
+} g_keysym_to_unicode_table[] = {
+  // Control characters
+  {XK_BackSpace,                   0x0008},
+  {XK_Tab,                         0x0009},
+  {XK_Linefeed,                    0x000a},
+  {XK_Clear,                       0x000b},
+  {XK_Return,                      0x000d},
+  {XK_Escape,                      0x001b},
+  {XK_Delete,                      0x007f},
+
+  // Numeric keypad
+  {XK_KP_Space,                    0x0020},
+  {XK_KP_Tab,                      0x0009},
+  {XK_KP_Enter,                    0x000d},
+  {XK_KP_Equal,                    0x003d},
+  {XK_KP_Multiply,                 0x002a},
+  {XK_KP_Add,                      0x002b},
+  {XK_KP_Separator,                0x002c},
+  {XK_KP_Subtract,                 0x002d},
+  {XK_KP_Decimal,                  0x002e},
+  {XK_KP_Divide,                   0x002f},
+  {XK_KP_0,                        0x0030},
+  {XK_KP_1,                        0x0031},
+  {XK_KP_2,                        0x0032},
+  {XK_KP_3,                        0x0033},
+  {XK_KP_4,                        0x0034},
+  {XK_KP_5,                        0x0035},
+  {XK_KP_6,                        0x0036},
+  {XK_KP_7,                        0x0037},
+  {XK_KP_8,                        0x0038},
+  {XK_KP_9,                        0x0039},
+
+  // Latin 1 KeySyms map 1:1 to Unicode
+
+  // Latin 2
+  {XK_Aogonek,                     0x0104},  // LATIN CAPITAL LETTER A WITH OGONEK
+  {XK_breve,                       0x02D8},  // BREVE
+  {XK_Lstroke,                     0x0141},  // LATIN CAPITAL LETTER L WITH STROKE
+  {XK_Lcaron,                      0x013D},  // LATIN CAPITAL LETTER L WITH CARON
+  {XK_Sacute,                      0x015A},  // LATIN CAPITAL LETTER S WITH ACUTE
+  {XK_Scaron,                      0x0160},  // LATIN CAPITAL LETTER S WITH CARON
+  {XK_Scedilla,                    0x015E},  // LATIN CAPITAL LETTER S WITH CEDILLA
+  {XK_Tcaron,                      0x0164},  // LATIN CAPITAL LETTER T WITH CARON
+  {XK_Zacute,                      0x0179},  // LATIN CAPITAL LETTER Z WITH ACUTE
+  {XK_Zcaron,                      0x017D},  // LATIN CAPITAL LETTER Z WITH CARON
+  {XK_Zabovedot,                   0x017B},  // LATIN CAPITAL LETTER Z WITH DOT ABOVE
+  {XK_aogonek,                     0x0105},  // LATIN SMALL LETTER A WITH OGONEK
+  {XK_ogonek,                      0x02DB},  // OGONEK
+  {XK_lstroke,                     0x0142},  // LATIN SMALL LETTER L WITH STROKE
+  {XK_lcaron,                      0x013E},  // LATIN SMALL LETTER L WITH CARON
+  {XK_sacute,                      0x015B},  // LATIN SMALL LETTER S WITH ACUTE
+  {XK_caron,                       0x02C7},  // CARON
+  {XK_scaron,                      0x0161},  // LATIN SMALL LETTER S WITH CARON
+  {XK_scedilla,                    0x015F},  // LATIN SMALL LETTER S WITH CEDILLA
+  {XK_tcaron,                      0x0165},  // LATIN SMALL LETTER T WITH CARON
+  {XK_zacute,                      0x017A},  // LATIN SMALL LETTER Z WITH ACUTE
+  {XK_doubleacute,                 0x02DD},  // DOUBLE ACUTE ACCENT
+  {XK_zcaron,                      0x017E},  // LATIN SMALL LETTER Z WITH CARON
+  {XK_zabovedot,                   0x017C},  // LATIN SMALL LETTER Z WITH DOT ABOVE
+  {XK_Racute,                      0x0154},  // LATIN CAPITAL LETTER R WITH ACUTE
+  {XK_Abreve,                      0x0102},  // LATIN CAPITAL LETTER A WITH BREVE
+  {XK_Lacute,                      0x0139},  // LATIN CAPITAL LETTER L WITH ACUTE
+  {XK_Cacute,                      0x0106},  // LATIN CAPITAL LETTER C WITH ACUTE
+  {XK_Ccaron,                      0x010C},  // LATIN CAPITAL LETTER C WITH CARON
+  {XK_Eogonek,                     0x0118},  // LATIN CAPITAL LETTER E WITH OGONEK
+  {XK_Ecaron,                      0x011A},  // LATIN CAPITAL LETTER E WITH CARON
+  {XK_Dcaron,                      0x010E},  // LATIN CAPITAL LETTER D WITH CARON
+  {XK_Dstroke,                     0x0110},  // LATIN CAPITAL LETTER D WITH STROKE
+  {XK_Nacute,                      0x0143},  // LATIN CAPITAL LETTER N WITH ACUTE
+  {XK_Ncaron,                      0x0147},  // LATIN CAPITAL LETTER N WITH CARON
+  {XK_Odoubleacute,                0x0150},  // LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+  {XK_Rcaron,                      0x0158},  // LATIN CAPITAL LETTER R WITH CARON
+  {XK_Uring,                       0x016E},  // LATIN CAPITAL LETTER U WITH RING ABOVE
+  {XK_Udoubleacute,                0x0170},  // LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+  {XK_Tcedilla,                    0x0162},  // LATIN CAPITAL LETTER T WITH CEDILLA
+  {XK_racute,                      0x0155},  // LATIN SMALL LETTER R WITH ACUTE
+  {XK_abreve,                      0x0103},  // LATIN SMALL LETTER A WITH BREVE
+  {XK_lacute,                      0x013A},  // LATIN SMALL LETTER L WITH ACUTE
+  {XK_cacute,                      0x0107},  // LATIN SMALL LETTER C WITH ACUTE
+  {XK_ccaron,                      0x010D},  // LATIN SMALL LETTER C WITH CARON
+  {XK_eogonek,                     0x0119},  // LATIN SMALL LETTER E WITH OGONEK
+  {XK_ecaron,                      0x011B},  // LATIN SMALL LETTER E WITH CARON
+  {XK_dcaron,                      0x010F},  // LATIN SMALL LETTER D WITH CARON
+  {XK_dstroke,                     0x0111},  // LATIN SMALL LETTER D WITH STROKE
+  {XK_nacute,                      0x0144},  // LATIN SMALL LETTER N WITH ACUTE
+  {XK_ncaron,                      0x0148},  // LATIN SMALL LETTER N WITH CARON
+  {XK_odoubleacute,                0x0151},  // LATIN SMALL LETTER O WITH DOUBLE ACUTE
+  {XK_rcaron,                      0x0159},  // LATIN SMALL LETTER R WITH CARON
+  {XK_uring,                       0x016F},  // LATIN SMALL LETTER U WITH RING ABOVE
+  {XK_udoubleacute,                0x0171},  // LATIN SMALL LETTER U WITH DOUBLE ACUTE
+  {XK_tcedilla,                    0x0163},  // LATIN SMALL LETTER T WITH CEDILLA
+  {XK_abovedot,                    0x02D9},  // DOT ABOVE
+
+  // Latin 3
+  {XK_Hstroke,                     0x0126},  // LATIN CAPITAL LETTER H WITH STROKE
+  {XK_Hcircumflex,                 0x0124},  // LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+  {XK_Iabovedot,                   0x0130},  // LATIN CAPITAL LETTER I WITH DOT ABOVE
+  {XK_Gbreve,                      0x011E},  // LATIN CAPITAL LETTER G WITH BREVE
+  {XK_Jcircumflex,                 0x0134},  // LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+  {XK_hstroke,                     0x0127},  // LATIN SMALL LETTER H WITH STROKE
+  {XK_hcircumflex,                 0x0125},  // LATIN SMALL LETTER H WITH CIRCUMFLEX
+  {XK_idotless,                    0x0131},  // LATIN SMALL LETTER DOTLESS I
+  {XK_gbreve,                      0x011F},  // LATIN SMALL LETTER G WITH BREVE
+  {XK_jcircumflex,                 0x0135},  // LATIN SMALL LETTER J WITH CIRCUMFLEX
+  {XK_Cabovedot,                   0x010A},  // LATIN CAPITAL LETTER C WITH DOT ABOVE
+  {XK_Ccircumflex,                 0x0108},  // LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+  {XK_Gabovedot,                   0x0120},  // LATIN CAPITAL LETTER G WITH DOT ABOVE
+  {XK_Gcircumflex,                 0x011C},  // LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+  {XK_Ubreve,                      0x016C},  // LATIN CAPITAL LETTER U WITH BREVE
+  {XK_Scircumflex,                 0x015C},  // LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+  {XK_cabovedot,                   0x010B},  // LATIN SMALL LETTER C WITH DOT ABOVE
+  {XK_ccircumflex,                 0x0109},  // LATIN SMALL LETTER C WITH CIRCUMFLEX
+  {XK_gabovedot,                   0x0121},  // LATIN SMALL LETTER G WITH DOT ABOVE
+  {XK_gcircumflex,                 0x011D},  // LATIN SMALL LETTER G WITH CIRCUMFLEX
+  {XK_ubreve,                      0x016D},  // LATIN SMALL LETTER U WITH BREVE
+  {XK_scircumflex,                 0x015D},  // LATIN SMALL LETTER S WITH CIRCUMFLEX
+
+  // Latin 4
+  {XK_kra,                         0x0138},  // LATIN SMALL LETTER KRA
+  {XK_Rcedilla,                    0x0156},  // LATIN CAPITAL LETTER R WITH CEDILLA
+  {XK_Itilde,                      0x0128},  // LATIN CAPITAL LETTER I WITH TILDE
+  {XK_Lcedilla,                    0x013B},  // LATIN CAPITAL LETTER L WITH CEDILLA
+  {XK_Emacron,                     0x0112},  // LATIN CAPITAL LETTER E WITH MACRON
+  {XK_Gcedilla,                    0x0122},  // LATIN CAPITAL LETTER G WITH CEDILLA
+  {XK_Tslash,                      0x0166},  // LATIN CAPITAL LETTER T WITH STROKE
+  {XK_rcedilla,                    0x0157},  // LATIN SMALL LETTER R WITH CEDILLA
+  {XK_itilde,                      0x0129},  // LATIN SMALL LETTER I WITH TILDE
+  {XK_lcedilla,                    0x013C},  // LATIN SMALL LETTER L WITH CEDILLA
+  {XK_emacron,                     0x0113},  // LATIN SMALL LETTER E WITH MACRON
+  {XK_gcedilla,                    0x0123},  // LATIN SMALL LETTER G WITH CEDILLA
+  {XK_tslash,                      0x0167},  // LATIN SMALL LETTER T WITH STROKE
+  {XK_ENG,                         0x014A},  // LATIN CAPITAL LETTER ENG
+  {XK_eng,                         0x014B},  // LATIN SMALL LETTER ENG
+  {XK_Amacron,                     0x0100},  // LATIN CAPITAL LETTER A WITH MACRON
+  {XK_Iogonek,                     0x012E},  // LATIN CAPITAL LETTER I WITH OGONEK
+  {XK_Eabovedot,                   0x0116},  // LATIN CAPITAL LETTER E WITH DOT ABOVE
+  {XK_Imacron,                     0x012A},  // LATIN CAPITAL LETTER I WITH MACRON
+  {XK_Ncedilla,                    0x0145},  // LATIN CAPITAL LETTER N WITH CEDILLA
+  {XK_Omacron,                     0x014C},  // LATIN CAPITAL LETTER O WITH MACRON
+  {XK_Kcedilla,                    0x0136},  // LATIN CAPITAL LETTER K WITH CEDILLA
+  {XK_Uogonek,                     0x0172},  // LATIN CAPITAL LETTER U WITH OGONEK
+  {XK_Utilde,                      0x0168},  // LATIN CAPITAL LETTER U WITH TILDE
+  {XK_Umacron,                     0x016A},  // LATIN CAPITAL LETTER U WITH MACRON
+  {XK_amacron,                     0x0101},  // LATIN SMALL LETTER A WITH MACRON
+  {XK_iogonek,                     0x012F},  // LATIN SMALL LETTER I WITH OGONEK
+  {XK_eabovedot,                   0x0117},  // LATIN SMALL LETTER E WITH DOT ABOVE
+  {XK_imacron,                     0x012B},  // LATIN SMALL LETTER I WITH MACRON
+  {XK_ncedilla,                    0x0146},  // LATIN SMALL LETTER N WITH CEDILLA
+  {XK_omacron,                     0x014D},  // LATIN SMALL LETTER O WITH MACRON
+  {XK_kcedilla,                    0x0137},  // LATIN SMALL LETTER K WITH CEDILLA
+  {XK_uogonek,                     0x0173},  // LATIN SMALL LETTER U WITH OGONEK
+  {XK_utilde,                      0x0169},  // LATIN SMALL LETTER U WITH TILDE
+  {XK_umacron,                     0x016B},  // LATIN SMALL LETTER U WITH MACRON
+
+  // Latin 8 KeySyms map 1:1 to Unicode
+
+  // Latin 9
+  {XK_OE,                          0x0152},  // LATIN CAPITAL LIGATURE OE
+  {XK_oe,                          0x0153},  // LATIN SMALL LIGATURE OE
+  {XK_Ydiaeresis,                  0x0178},  // LATIN CAPITAL LETTER Y WITH DIAERESIS
+
+  // Katakana
+  {XK_overline,                    0x203E},  // OVERLINE
+  {XK_kana_fullstop,               0x3002},  // IDEOGRAPHIC FULL STOP
+  {XK_kana_openingbracket,         0x300C},  // LEFT CORNER BRACKET
+  {XK_kana_closingbracket,         0x300D},  // RIGHT CORNER BRACKET
+  {XK_kana_comma,                  0x3001},  // IDEOGRAPHIC COMMA
+  {XK_kana_conjunctive,            0x30FB},  // KATAKANA MIDDLE DOT
+  {XK_kana_WO,                     0x30F2},  // KATAKANA LETTER WO
+  {XK_kana_a,                      0x30A1},  // KATAKANA LETTER SMALL A
+  {XK_kana_i,                      0x30A3},  // KATAKANA LETTER SMALL I
+  {XK_kana_u,                      0x30A5},  // KATAKANA LETTER SMALL U
+  {XK_kana_e,                      0x30A7},  // KATAKANA LETTER SMALL E
+  {XK_kana_o,                      0x30A9},  // KATAKANA LETTER SMALL O
+  {XK_kana_ya,                     0x30E3},  // KATAKANA LETTER SMALL YA
+  {XK_kana_yu,                     0x30E5},  // KATAKANA LETTER SMALL YU
+  {XK_kana_yo,                     0x30E7},  // KATAKANA LETTER SMALL YO
+  {XK_kana_tsu,                    0x30C3},  // KATAKANA LETTER SMALL TU
+  {XK_prolongedsound,              0x30FC},  // KATAKANA-HIRAGANA PROLONGED SOUND MARK
+  {XK_kana_A,                      0x30A2},  // KATAKANA LETTER A
+  {XK_kana_I,                      0x30A4},  // KATAKANA LETTER I
+  {XK_kana_U,                      0x30A6},  // KATAKANA LETTER U
+  {XK_kana_E,                      0x30A8},  // KATAKANA LETTER E
+  {XK_kana_O,                      0x30AA},  // KATAKANA LETTER O
+  {XK_kana_KA,                     0x30AB},  // KATAKANA LETTER KA
+  {XK_kana_KI,                     0x30AD},  // KATAKANA LETTER KI
+  {XK_kana_KU,                     0x30AF},  // KATAKANA LETTER KU
+  {XK_kana_KE,                     0x30B1},  // KATAKANA LETTER KE
+  {XK_kana_KO,                     0x30B3},  // KATAKANA LETTER KO
+  {XK_kana_SA,                     0x30B5},  // KATAKANA LETTER SA
+  {XK_kana_SHI,                    0x30B7},  // KATAKANA LETTER SI
+  {XK_kana_SU,                     0x30B9},  // KATAKANA LETTER SU
+  {XK_kana_SE,                     0x30BB},  // KATAKANA LETTER SE
+  {XK_kana_SO,                     0x30BD},  // KATAKANA LETTER SO
+  {XK_kana_TA,                     0x30BF},  // KATAKANA LETTER TA
+  {XK_kana_CHI,                    0x30C1},  // KATAKANA LETTER TI
+  {XK_kana_TSU,                    0x30C4},  // KATAKANA LETTER TU
+  {XK_kana_TE,                     0x30C6},  // KATAKANA LETTER TE
+  {XK_kana_TO,                     0x30C8},  // KATAKANA LETTER TO
+  {XK_kana_NA,                     0x30CA},  // KATAKANA LETTER NA
+  {XK_kana_NI,                     0x30CB},  // KATAKANA LETTER NI
+  {XK_kana_NU,                     0x30CC},  // KATAKANA LETTER NU
+  {XK_kana_NE,                     0x30CD},  // KATAKANA LETTER NE
+  {XK_kana_NO,                     0x30CE},  // KATAKANA LETTER NO
+  {XK_kana_HA,                     0x30CF},  // KATAKANA LETTER HA
+  {XK_kana_HI,                     0x30D2},  // KATAKANA LETTER HI
+  {XK_kana_FU,                     0x30D5},  // KATAKANA LETTER HU
+  {XK_kana_HE,                     0x30D8},  // KATAKANA LETTER HE
+  {XK_kana_HO,                     0x30DB},  // KATAKANA LETTER HO
+  {XK_kana_MA,                     0x30DE},  // KATAKANA LETTER MA
+  {XK_kana_MI,                     0x30DF},  // KATAKANA LETTER MI
+  {XK_kana_MU,                     0x30E0},  // KATAKANA LETTER MU
+  {XK_kana_ME,                     0x30E1},  // KATAKANA LETTER ME
+  {XK_kana_MO,                     0x30E2},  // KATAKANA LETTER MO
+  {XK_kana_YA,                     0x30E4},  // KATAKANA LETTER YA
+  {XK_kana_YU,                     0x30E6},  // KATAKANA LETTER YU
+  {XK_kana_YO,                     0x30E8},  // KATAKANA LETTER YO
+  {XK_kana_RA,                     0x30E9},  // KATAKANA LETTER RA
+  {XK_kana_RI,                     0x30EA},  // KATAKANA LETTER RI
+  {XK_kana_RU,                     0x30EB},  // KATAKANA LETTER RU
+  {XK_kana_RE,                     0x30EC},  // KATAKANA LETTER RE
+  {XK_kana_RO,                     0x30ED},  // KATAKANA LETTER RO
+  {XK_kana_WA,                     0x30EF},  // KATAKANA LETTER WA
+  {XK_kana_N,                      0x30F3},  // KATAKANA LETTER N
+  {XK_voicedsound,                 0x309B},  // KATAKANA-HIRAGANA VOICED SOUND MARK
+  {XK_semivoicedsound,             0x309C},  // KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+
+  // Arabic KeySyms partially map 1:1 to Unicode
+  {XK_Arabic_comma,                0x060C},  // ARABIC COMMA
+  {XK_Arabic_semicolon,            0x061B},  // ARABIC SEMICOLON
+  {XK_Arabic_question_mark,        0x061F},  // ARABIC QUESTION MARK
+  {XK_Arabic_hamza,                0x0621},  // ARABIC LETTER HAMZA
+  {XK_Arabic_maddaonalef,          0x0622},  // ARABIC LETTER ALEF WITH MADDA ABOVE
+  {XK_Arabic_hamzaonalef,          0x0623},  // ARABIC LETTER ALEF WITH HAMZA ABOVE
+  {XK_Arabic_hamzaonwaw,           0x0624},  // ARABIC LETTER WAW WITH HAMZA ABOVE
+  {XK_Arabic_hamzaunderalef,       0x0625},  // ARABIC LETTER ALEF WITH HAMZA BELOW
+  {XK_Arabic_hamzaonyeh,           0x0626},  // ARABIC LETTER YEH WITH HAMZA ABOVE
+  {XK_Arabic_alef,                 0x0627},  // ARABIC LETTER ALEF
+  {XK_Arabic_beh,                  0x0628},  // ARABIC LETTER BEH
+  {XK_Arabic_tehmarbuta,           0x0629},  // ARABIC LETTER TEH MARBUTA
+  {XK_Arabic_teh,                  0x062A},  // ARABIC LETTER TEH
+  {XK_Arabic_theh,                 0x062B},  // ARABIC LETTER THEH
+  {XK_Arabic_jeem,                 0x062C},  // ARABIC LETTER JEEM
+  {XK_Arabic_hah,                  0x062D},  // ARABIC LETTER HAH
+  {XK_Arabic_khah,                 0x062E},  // ARABIC LETTER KHAH
+  {XK_Arabic_dal,                  0x062F},  // ARABIC LETTER DAL
+  {XK_Arabic_thal,                 0x0630},  // ARABIC LETTER THAL
+  {XK_Arabic_ra,                   0x0631},  // ARABIC LETTER REH
+  {XK_Arabic_zain,                 0x0632},  // ARABIC LETTER ZAIN
+  {XK_Arabic_seen,                 0x0633},  // ARABIC LETTER SEEN
+  {XK_Arabic_sheen,                0x0634},  // ARABIC LETTER SHEEN
+  {XK_Arabic_sad,                  0x0635},  // ARABIC LETTER SAD
+  {XK_Arabic_dad,                  0x0636},  // ARABIC LETTER DAD
+  {XK_Arabic_tah,                  0x0637},  // ARABIC LETTER TAH
+  {XK_Arabic_zah,                  0x0638},  // ARABIC LETTER ZAH
+  {XK_Arabic_ain,                  0x0639},  // ARABIC LETTER AIN
+  {XK_Arabic_ghain,                0x063A},  // ARABIC LETTER GHAIN
+  {XK_Arabic_tatweel,              0x0640},  // ARABIC TATWEEL
+  {XK_Arabic_feh,                  0x0641},  // ARABIC LETTER FEH
+  {XK_Arabic_qaf,                  0x0642},  // ARABIC LETTER QAF
+  {XK_Arabic_kaf,                  0x0643},  // ARABIC LETTER KAF
+  {XK_Arabic_lam,                  0x0644},  // ARABIC LETTER LAM
+  {XK_Arabic_meem,                 0x0645},  // ARABIC LETTER MEEM
+  {XK_Arabic_noon,                 0x0646},  // ARABIC LETTER NOON
+  {XK_Arabic_ha,                   0x0647},  // ARABIC LETTER HEH
+  {XK_Arabic_waw,                  0x0648},  // ARABIC LETTER WAW
+  {XK_Arabic_alefmaksura,          0x0649},  // ARABIC LETTER ALEF MAKSURA
+  {XK_Arabic_yeh,                  0x064A},  // ARABIC LETTER YEH
+  {XK_Arabic_fathatan,             0x064B},  // ARABIC FATHATAN
+  {XK_Arabic_dammatan,             0x064C},  // ARABIC DAMMATAN
+  {XK_Arabic_kasratan,             0x064D},  // ARABIC KASRATAN
+  {XK_Arabic_fatha,                0x064E},  // ARABIC FATHA
+  {XK_Arabic_damma,                0x064F},  // ARABIC DAMMA
+  {XK_Arabic_kasra,                0x0650},  // ARABIC KASRA
+  {XK_Arabic_shadda,               0x0651},  // ARABIC SHADDA
+  {XK_Arabic_sukun,                0x0652},  // ARABIC SUKUN
+
+  // Cyrillic KeySyms partially map 1:1 to Unicode
+  {XK_Serbian_dje,                 0x0452},  // CYRILLIC SMALL LETTER DJE
+  {XK_Macedonia_gje,               0x0453},  // CYRILLIC SMALL LETTER GJE
+  {XK_Cyrillic_io,                 0x0451},  // CYRILLIC SMALL LETTER IO
+  {XK_Ukrainian_ie,                0x0454},  // CYRILLIC SMALL LETTER UKRAINIAN IE
+  {XK_Macedonia_dse,               0x0455},  // CYRILLIC SMALL LETTER DZE
+  {XK_Ukrainian_i,                 0x0456},  // CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+  {XK_Ukrainian_yi,                0x0457},  // CYRILLIC SMALL LETTER YI
+  {XK_Cyrillic_je,                 0x0458},  // CYRILLIC SMALL LETTER JE
+  {XK_Cyrillic_lje,                0x0459},  // CYRILLIC SMALL LETTER LJE
+  {XK_Cyrillic_nje,                0x045A},  // CYRILLIC SMALL LETTER NJE
+  {XK_Serbian_tshe,                0x045B},  // CYRILLIC SMALL LETTER TSHE
+  {XK_Macedonia_kje,               0x045C},  // CYRILLIC SMALL LETTER KJE
+  {XK_Ukrainian_ghe_with_upturn,   0x0491},  // CYRILLIC SMALL LETTER GHE WITH UPTURN
+  {XK_Byelorussian_shortu,         0x045E},  // CYRILLIC SMALL LETTER SHORT U
+  {XK_Cyrillic_dzhe,               0x045F},  // CYRILLIC SMALL LETTER DZHE
+  {XK_numerosign,                  0x2116},  // NUMERO SIGN
+  {XK_Serbian_DJE,                 0x0402},  // CYRILLIC CAPITAL LETTER DJE
+  {XK_Macedonia_GJE,               0x0403},  // CYRILLIC CAPITAL LETTER GJE
+  {XK_Cyrillic_IO,                 0x0401},  // CYRILLIC CAPITAL LETTER IO
+  {XK_Ukrainian_IE,                0x0404},  // CYRILLIC CAPITAL LETTER UKRAINIAN IE
+  {XK_Macedonia_DSE,               0x0405},  // CYRILLIC CAPITAL LETTER DZE
+  {XK_Ukrainian_I,                 0x0406},  // CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+  {XK_Ukrainian_YI,                0x0407},  // CYRILLIC CAPITAL LETTER YI
+  {XK_Cyrillic_JE,                 0x0408},  // CYRILLIC CAPITAL LETTER JE
+  {XK_Cyrillic_LJE,                0x0409},  // CYRILLIC CAPITAL LETTER LJE
+  {XK_Cyrillic_NJE,                0x040A},  // CYRILLIC CAPITAL LETTER NJE
+  {XK_Serbian_TSHE,                0x040B},  // CYRILLIC CAPITAL LETTER TSHE
+  {XK_Macedonia_KJE,               0x040C},  // CYRILLIC CAPITAL LETTER KJE
+  {XK_Ukrainian_GHE_WITH_UPTURN,   0x0490},  // CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+  {XK_Byelorussian_SHORTU,         0x040E},  // CYRILLIC CAPITAL LETTER SHORT U
+  {XK_Cyrillic_DZHE,               0x040F},  // CYRILLIC CAPITAL LETTER DZHE
+  {XK_Cyrillic_yu,                 0x044E},  // CYRILLIC SMALL LETTER YU
+  {XK_Cyrillic_a,                  0x0430},  // CYRILLIC SMALL LETTER A
+  {XK_Cyrillic_be,                 0x0431},  // CYRILLIC SMALL LETTER BE
+  {XK_Cyrillic_tse,                0x0446},  // CYRILLIC SMALL LETTER TSE
+  {XK_Cyrillic_de,                 0x0434},  // CYRILLIC SMALL LETTER DE
+  {XK_Cyrillic_ie,                 0x0435},  // CYRILLIC SMALL LETTER IE
+  {XK_Cyrillic_ef,                 0x0444},  // CYRILLIC SMALL LETTER EF
+  {XK_Cyrillic_ghe,                0x0433},  // CYRILLIC SMALL LETTER GHE
+  {XK_Cyrillic_ha,                 0x0445},  // CYRILLIC SMALL LETTER HA
+  {XK_Cyrillic_i,                  0x0438},  // CYRILLIC SMALL LETTER I
+  {XK_Cyrillic_shorti,             0x0439},  // CYRILLIC SMALL LETTER SHORT I
+  {XK_Cyrillic_ka,                 0x043A},  // CYRILLIC SMALL LETTER KA
+  {XK_Cyrillic_el,                 0x043B},  // CYRILLIC SMALL LETTER EL
+  {XK_Cyrillic_em,                 0x043C},  // CYRILLIC SMALL LETTER EM
+  {XK_Cyrillic_en,                 0x043D},  // CYRILLIC SMALL LETTER EN
+  {XK_Cyrillic_o,                  0x043E},  // CYRILLIC SMALL LETTER O
+  {XK_Cyrillic_pe,                 0x043F},  // CYRILLIC SMALL LETTER PE
+  {XK_Cyrillic_ya,                 0x044F},  // CYRILLIC SMALL LETTER YA
+  {XK_Cyrillic_er,                 0x0440},  // CYRILLIC SMALL LETTER ER
+  {XK_Cyrillic_es,                 0x0441},  // CYRILLIC SMALL LETTER ES
+  {XK_Cyrillic_te,                 0x0442},  // CYRILLIC SMALL LETTER TE
+  {XK_Cyrillic_u,                  0x0443},  // CYRILLIC SMALL LETTER U
+  {XK_Cyrillic_zhe,                0x0436},  // CYRILLIC SMALL LETTER ZHE
+  {XK_Cyrillic_ve,                 0x0432},  // CYRILLIC SMALL LETTER VE
+  {XK_Cyrillic_softsign,           0x044C},  // CYRILLIC SMALL LETTER SOFT SIGN
+  {XK_Cyrillic_yeru,               0x044B},  // CYRILLIC SMALL LETTER YERU
+  {XK_Cyrillic_ze,                 0x0437},  // CYRILLIC SMALL LETTER ZE
+  {XK_Cyrillic_sha,                0x0448},  // CYRILLIC SMALL LETTER SHA
+  {XK_Cyrillic_e,                  0x044D},  // CYRILLIC SMALL LETTER E
+  {XK_Cyrillic_shcha,              0x0449},  // CYRILLIC SMALL LETTER SHCHA
+  {XK_Cyrillic_che,                0x0447},  // CYRILLIC SMALL LETTER CHE
+  {XK_Cyrillic_hardsign,           0x044A},  // CYRILLIC SMALL LETTER HARD SIGN
+  {XK_Cyrillic_YU,                 0x042E},  // CYRILLIC CAPITAL LETTER YU
+  {XK_Cyrillic_A,                  0x0410},  // CYRILLIC CAPITAL LETTER A
+  {XK_Cyrillic_BE,                 0x0411},  // CYRILLIC CAPITAL LETTER BE
+  {XK_Cyrillic_TSE,                0x0426},  // CYRILLIC CAPITAL LETTER TSE
+  {XK_Cyrillic_DE,                 0x0414},  // CYRILLIC CAPITAL LETTER DE
+  {XK_Cyrillic_IE,                 0x0415},  // CYRILLIC CAPITAL LETTER IE
+  {XK_Cyrillic_EF,                 0x0424},  // CYRILLIC CAPITAL LETTER EF
+  {XK_Cyrillic_GHE,                0x0413},  // CYRILLIC CAPITAL LETTER GHE
+  {XK_Cyrillic_HA,                 0x0425},  // CYRILLIC CAPITAL LETTER HA
+  {XK_Cyrillic_I,                  0x0418},  // CYRILLIC CAPITAL LETTER I
+  {XK_Cyrillic_SHORTI,             0x0419},  // CYRILLIC CAPITAL LETTER SHORT I
+  {XK_Cyrillic_KA,                 0x041A},  // CYRILLIC CAPITAL LETTER KA
+  {XK_Cyrillic_EL,                 0x041B},  // CYRILLIC CAPITAL LETTER EL
+  {XK_Cyrillic_EM,                 0x041C},  // CYRILLIC CAPITAL LETTER EM
+  {XK_Cyrillic_EN,                 0x041D},  // CYRILLIC CAPITAL LETTER EN
+  {XK_Cyrillic_O,                  0x041E},  // CYRILLIC CAPITAL LETTER O
+  {XK_Cyrillic_PE,                 0x041F},  // CYRILLIC CAPITAL LETTER PE
+  {XK_Cyrillic_YA,                 0x042F},  // CYRILLIC CAPITAL LETTER YA
+  {XK_Cyrillic_ER,                 0x0420},  // CYRILLIC CAPITAL LETTER ER
+  {XK_Cyrillic_ES,                 0x0421},  // CYRILLIC CAPITAL LETTER ES
+  {XK_Cyrillic_TE,                 0x0422},  // CYRILLIC CAPITAL LETTER TE
+  {XK_Cyrillic_U,                  0x0423},  // CYRILLIC CAPITAL LETTER U
+  {XK_Cyrillic_ZHE,                0x0416},  // CYRILLIC CAPITAL LETTER ZHE
+  {XK_Cyrillic_VE,                 0x0412},  // CYRILLIC CAPITAL LETTER VE
+  {XK_Cyrillic_SOFTSIGN,           0x042C},  // CYRILLIC CAPITAL LETTER SOFT SIGN
+  {XK_Cyrillic_YERU,               0x042B},  // CYRILLIC CAPITAL LETTER YERU
+  {XK_Cyrillic_ZE,                 0x0417},  // CYRILLIC CAPITAL LETTER ZE
+  {XK_Cyrillic_SHA,                0x0428},  // CYRILLIC CAPITAL LETTER SHA
+  {XK_Cyrillic_E,                  0x042D},  // CYRILLIC CAPITAL LETTER E
+  {XK_Cyrillic_SHCHA,              0x0429},  // CYRILLIC CAPITAL LETTER SHCHA
+  {XK_Cyrillic_CHE,                0x0427},  // CYRILLIC CAPITAL LETTER CHE
+  {XK_Cyrillic_HARDSIGN,           0x042A},  // CYRILLIC CAPITAL LETTER HARD SIGN
+
+  // Greek
+  {XK_Greek_ALPHAaccent,           0x0386},  // GREEK CAPITAL LETTER ALPHA WITH TONOS
+  {XK_Greek_EPSILONaccent,         0x0388},  // GREEK CAPITAL LETTER EPSILON WITH TONOS
+  {XK_Greek_ETAaccent,             0x0389},  // GREEK CAPITAL LETTER ETA WITH TONOS
+  {XK_Greek_IOTAaccent,            0x038A},  // GREEK CAPITAL LETTER IOTA WITH TONOS
+  {XK_Greek_IOTAdieresis,          0x03AA},  // GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+  {XK_Greek_OMICRONaccent,         0x038C},  // GREEK CAPITAL LETTER OMICRON WITH TONOS
+  {XK_Greek_UPSILONaccent,         0x038E},  // GREEK CAPITAL LETTER UPSILON WITH TONOS
+  {XK_Greek_UPSILONdieresis,       0x03AB},  // GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+  {XK_Greek_OMEGAaccent,           0x038F},  // GREEK CAPITAL LETTER OMEGA WITH TONOS
+  {XK_Greek_accentdieresis,        0x0385},  // GREEK DIALYTIKA TONOS
+  {XK_Greek_horizbar,              0x2015},  // HORIZONTAL BAR
+  {XK_Greek_alphaaccent,           0x03AC},  // GREEK SMALL LETTER ALPHA WITH TONOS
+  {XK_Greek_epsilonaccent,         0x03AD},  // GREEK SMALL LETTER EPSILON WITH TONOS
+  {XK_Greek_etaaccent,             0x03AE},  // GREEK SMALL LETTER ETA WITH TONOS
+  {XK_Greek_iotaaccent,            0x03AF},  // GREEK SMALL LETTER IOTA WITH TONOS
+  {XK_Greek_iotadieresis,          0x03CA},  // GREEK SMALL LETTER IOTA WITH DIALYTIKA
+  {XK_Greek_iotaaccentdieresis,    0x0390},  // GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+  {XK_Greek_omicronaccent,         0x03CC},  // GREEK SMALL LETTER OMICRON WITH TONOS
+  {XK_Greek_upsilonaccent,         0x03CD},  // GREEK SMALL LETTER UPSILON WITH TONOS
+  {XK_Greek_upsilondieresis,       0x03CB},  // GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+  {XK_Greek_upsilonaccentdieresis, 0x03B0},  // GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+  {XK_Greek_omegaaccent,           0x03CE},  // GREEK SMALL LETTER OMEGA WITH TONOS
+  {XK_Greek_ALPHA,                 0x0391},  // GREEK CAPITAL LETTER ALPHA
+  {XK_Greek_BETA,                  0x0392},  // GREEK CAPITAL LETTER BETA
+  {XK_Greek_GAMMA,                 0x0393},  // GREEK CAPITAL LETTER GAMMA
+  {XK_Greek_DELTA,                 0x0394},  // GREEK CAPITAL LETTER DELTA
+  {XK_Greek_EPSILON,               0x0395},  // GREEK CAPITAL LETTER EPSILON
+  {XK_Greek_ZETA,                  0x0396},  // GREEK CAPITAL LETTER ZETA
+  {XK_Greek_ETA,                   0x0397},  // GREEK CAPITAL LETTER ETA
+  {XK_Greek_THETA,                 0x0398},  // GREEK CAPITAL LETTER THETA
+  {XK_Greek_IOTA,                  0x0399},  // GREEK CAPITAL LETTER IOTA
+  {XK_Greek_KAPPA,                 0x039A},  // GREEK CAPITAL LETTER KAPPA
+  {XK_Greek_LAMDA,                 0x039B},  // GREEK CAPITAL LETTER LAMDA
+  {XK_Greek_LAMBDA,                0x039B},  // GREEK CAPITAL LETTER LAMDA
+  {XK_Greek_MU,                    0x039C},  // GREEK CAPITAL LETTER MU
+  {XK_Greek_NU,                    0x039D},  // GREEK CAPITAL LETTER NU
+  {XK_Greek_XI,                    0x039E},  // GREEK CAPITAL LETTER XI
+  {XK_Greek_OMICRON,               0x039F},  // GREEK CAPITAL LETTER OMICRON
+  {XK_Greek_PI,                    0x03A0},  // GREEK CAPITAL LETTER PI
+  {XK_Greek_RHO,                   0x03A1},  // GREEK CAPITAL LETTER RHO
+  {XK_Greek_SIGMA,                 0x03A3},  // GREEK CAPITAL LETTER SIGMA
+  {XK_Greek_TAU,                   0x03A4},  // GREEK CAPITAL LETTER TAU
+  {XK_Greek_UPSILON,               0x03A5},  // GREEK CAPITAL LETTER UPSILON
+  {XK_Greek_PHI,                   0x03A6},  // GREEK CAPITAL LETTER PHI
+  {XK_Greek_CHI,                   0x03A7},  // GREEK CAPITAL LETTER CHI
+  {XK_Greek_PSI,                   0x03A8},  // GREEK CAPITAL LETTER PSI
+  {XK_Greek_OMEGA,                 0x03A9},  // GREEK CAPITAL LETTER OMEGA
+  {XK_Greek_alpha,                 0x03B1},  // GREEK SMALL LETTER ALPHA
+  {XK_Greek_beta,                  0x03B2},  // GREEK SMALL LETTER BETA
+  {XK_Greek_gamma,                 0x03B3},  // GREEK SMALL LETTER GAMMA
+  {XK_Greek_delta,                 0x03B4},  // GREEK SMALL LETTER DELTA
+  {XK_Greek_epsilon,               0x03B5},  // GREEK SMALL LETTER EPSILON
+  {XK_Greek_zeta,                  0x03B6},  // GREEK SMALL LETTER ZETA
+  {XK_Greek_eta,                   0x03B7},  // GREEK SMALL LETTER ETA
+  {XK_Greek_theta,                 0x03B8},  // GREEK SMALL LETTER THETA
+  {XK_Greek_iota,                  0x03B9},  // GREEK SMALL LETTER IOTA
+  {XK_Greek_kappa,                 0x03BA},  // GREEK SMALL LETTER KAPPA
+  {XK_Greek_lamda,                 0x03BB},  // GREEK SMALL LETTER LAMDA
+  {XK_Greek_lambda,                0x03BB},  // GREEK SMALL LETTER LAMDA
+  {XK_Greek_mu,                    0x03BC},  // GREEK SMALL LETTER MU
+  {XK_Greek_nu,                    0x03BD},  // GREEK SMALL LETTER NU
+  {XK_Greek_xi,                    0x03BE},  // GREEK SMALL LETTER XI
+  {XK_Greek_omicron,               0x03BF},  // GREEK SMALL LETTER OMICRON
+  {XK_Greek_pi,                    0x03C0},  // GREEK SMALL LETTER PI
+  {XK_Greek_rho,                   0x03C1},  // GREEK SMALL LETTER RHO
+  {XK_Greek_sigma,                 0x03C3},  // GREEK SMALL LETTER SIGMA
+  {XK_Greek_finalsmallsigma,       0x03C2},  // GREEK SMALL LETTER FINAL SIGMA
+  {XK_Greek_tau,                   0x03C4},  // GREEK SMALL LETTER TAU
+  {XK_Greek_upsilon,               0x03C5},  // GREEK SMALL LETTER UPSILON
+  {XK_Greek_phi,                   0x03C6},  // GREEK SMALL LETTER PHI
+  {XK_Greek_chi,                   0x03C7},  // GREEK SMALL LETTER CHI
+  {XK_Greek_psi,                   0x03C8},  // GREEK SMALL LETTER PSI
+  {XK_Greek_omega,                 0x03C9},  // GREEK SMALL LETTER OMEGA
+
+  // Technical
+  {XK_leftradical,                 0x23B7},  // RADICAL SYMBOL BOTTOM
+  {XK_topleftradical,              0x250C},  // BOX DRAWINGS LIGHT DOWN AND RIGHT
+  {XK_horizconnector,              0x2500},  // BOX DRAWINGS LIGHT HORIZONTAL
+  {XK_topintegral,                 0x2320},  // TOP HALF INTEGRAL
+  {XK_botintegral,                 0x2321},  // BOTTOM HALF INTEGRAL
+  {XK_vertconnector,               0x2502},  // BOX DRAWINGS LIGHT VERTICAL
+  {XK_topleftsqbracket,            0x23A1},  // LEFT SQUARE BRACKET UPPER CORNER
+  {XK_botleftsqbracket,            0x23A3},  // LEFT SQUARE BRACKET LOWER CORNER
+  {XK_toprightsqbracket,           0x23A4},  // RIGHT SQUARE BRACKET UPPER CORNER
+  {XK_botrightsqbracket,           0x23A6},  // RIGHT SQUARE BRACKET LOWER CORNER
+  {XK_topleftparens,               0x239B},  // LEFT PARENTHESIS UPPER HOOK
+  {XK_botleftparens,               0x239D},  // LEFT PARENTHESIS LOWER HOOK
+  {XK_toprightparens,              0x239E},  // RIGHT PARENTHESIS UPPER HOOK
+  {XK_botrightparens,              0x23A0},  // RIGHT PARENTHESIS LOWER HOOK
+  {XK_leftmiddlecurlybrace,        0x23A8},  // LEFT CURLY BRACKET MIDDLE PIECE
+  {XK_rightmiddlecurlybrace,       0x23AC},  // RIGHT CURLY BRACKET MIDDLE PIECE
+  {XK_lessthanequal,               0x2264},  // LESS-THAN OR EQUAL TO
+  {XK_notequal,                    0x2260},  // NOT EQUAL TO
+  {XK_greaterthanequal,            0x2265},  // GREATER-THAN OR EQUAL TO
+  {XK_integral,                    0x222B},  // INTEGRAL
+  {XK_therefore,                   0x2234},  // THEREFORE
+  {XK_variation,                   0x221D},  // PROPORTIONAL TO
+  {XK_infinity,                    0x221E},  // INFINITY
+  {XK_nabla,                       0x2207},  // NABLA
+  {XK_approximate,                 0x223C},  // TILDE OPERATOR
+  {XK_similarequal,                0x2243},  // ASYMPTOTICALLY EQUAL TO
+  {XK_ifonlyif,                    0x21D4},  // LEFT RIGHT DOUBLE ARROW
+  {XK_implies,                     0x21D2},  // RIGHTWARDS DOUBLE ARROW
+  {XK_identical,                   0x2261},  // IDENTICAL TO
+  {XK_radical,                     0x221A},  // SQUARE ROOT
+  {XK_includedin,                  0x2282},  // SUBSET OF
+  {XK_includes,                    0x2283},  // SUPERSET OF
+  {XK_intersection,                0x2229},  // INTERSECTION
+  {XK_union,                       0x222A},  // UNION
+  {XK_logicaland,                  0x2227},  // LOGICAL AND
+  {XK_logicalor,                   0x2228},  // LOGICAL OR
+  {XK_partialderivative,           0x2202},  // PARTIAL DIFFERENTIAL
+  {XK_function,                    0x0192},  // LATIN SMALL LETTER F WITH HOOK
+  {XK_leftarrow,                   0x2190},  // LEFTWARDS ARROW
+  {XK_uparrow,                     0x2191},  // UPWARDS ARROW
+  {XK_rightarrow,                  0x2192},  // RIGHTWARDS ARROW
+  {XK_downarrow,                   0x2193},  // DOWNWARDS ARROW
+
+  // Special
+  {XK_soliddiamond,                0x25C6},  // BLACK DIAMOND
+  {XK_checkerboard,                0x2592},  // MEDIUM SHADE
+  {XK_ht,                          0x2409},  // SYMBOL FOR HORIZONTAL TABULATION
+  {XK_ff,                          0x240C},  // SYMBOL FOR FORM FEED
+  {XK_cr,                          0x240D},  // SYMBOL FOR CARRIAGE RETURN
+  {XK_lf,                          0x240A},  // SYMBOL FOR LINE FEED
+  {XK_nl,                          0x2424},  // SYMBOL FOR NEWLINE
+  {XK_vt,                          0x240B},  // SYMBOL FOR VERTICAL TABULATION
+  {XK_lowrightcorner,              0x2518},  // BOX DRAWINGS LIGHT UP AND LEFT
+  {XK_uprightcorner,               0x2510},  // BOX DRAWINGS LIGHT DOWN AND LEFT
+  {XK_upleftcorner,                0x250C},  // BOX DRAWINGS LIGHT DOWN AND RIGHT
+  {XK_lowleftcorner,               0x2514},  // BOX DRAWINGS LIGHT UP AND RIGHT
+  {XK_crossinglines,               0x253C},  // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+  {XK_horizlinescan1,              0x23BA},  // HORIZONTAL SCAN LINE-1
+  {XK_horizlinescan3,              0x23BB},  // HORIZONTAL SCAN LINE-3
+  {XK_horizlinescan5,              0x2500},  // BOX DRAWINGS LIGHT HORIZONTAL
+  {XK_horizlinescan7,              0x23BC},  // HORIZONTAL SCAN LINE-7
+  {XK_horizlinescan9,              0x23BD},  // HORIZONTAL SCAN LINE-9
+  {XK_leftt,                       0x251C},  // BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+  {XK_rightt,                      0x2524},  // BOX DRAWINGS LIGHT VERTICAL AND LEFT
+  {XK_bott,                        0x2534},  // BOX DRAWINGS LIGHT UP AND HORIZONTAL
+  {XK_topt,                        0x252C},  // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+  {XK_vertbar,                     0x2502},  // BOX DRAWINGS LIGHT VERTICAL
+
+  // Publishing
+  {XK_emspace,                     0x2003},  // EM SPACE
+  {XK_enspace,                     0x2002},  // EN SPACE
+  {XK_em3space,                    0x2004},  // THREE-PER-EM SPACE
+  {XK_em4space,                    0x2005},  // FOUR-PER-EM SPACE
+  {XK_digitspace,                  0x2007},  // FIGURE SPACE
+  {XK_punctspace,                  0x2008},  // PUNCTUATION SPACE
+  {XK_thinspace,                   0x2009},  // THIN SPACE
+  {XK_hairspace,                   0x200A},  // HAIR SPACE
+  {XK_emdash,                      0x2014},  // EM DASH
+  {XK_endash,                      0x2013},  // EN DASH
+  {XK_signifblank,                 0x2423},  // OPEN BOX
+  {XK_ellipsis,                    0x2026},  // HORIZONTAL ELLIPSIS
+  {XK_doubbaselinedot,             0x2025},  // TWO DOT LEADER
+  {XK_onethird,                    0x2153},  // VULGAR FRACTION ONE THIRD
+  {XK_twothirds,                   0x2154},  // VULGAR FRACTION TWO THIRDS
+  {XK_onefifth,                    0x2155},  // VULGAR FRACTION ONE FIFTH
+  {XK_twofifths,                   0x2156},  // VULGAR FRACTION TWO FIFTHS
+  {XK_threefifths,                 0x2157},  // VULGAR FRACTION THREE FIFTHS
+  {XK_fourfifths,                  0x2158},  // VULGAR FRACTION FOUR FIFTHS
+  {XK_onesixth,                    0x2159},  // VULGAR FRACTION ONE SIXTH
+  {XK_fivesixths,                  0x215A},  // VULGAR FRACTION FIVE SIXTHS
+  {XK_careof,                      0x2105},  // CARE OF
+  {XK_figdash,                     0x2012},  // FIGURE DASH
+  {XK_leftanglebracket,            0x27E8},  // MATHEMATICAL LEFT ANGLE BRACKET
+  {XK_decimalpoint,                0x002E},  // FULL STOP
+  {XK_rightanglebracket,           0x27E9},  // MATHEMATICAL RIGHT ANGLE BRACKET
+  {XK_oneeighth,                   0x215B},  // VULGAR FRACTION ONE EIGHTH
+  {XK_threeeighths,                0x215C},  // VULGAR FRACTION THREE EIGHTHS
+  {XK_fiveeighths,                 0x215D},  // VULGAR FRACTION FIVE EIGHTHS
+  {XK_seveneighths,                0x215E},  // VULGAR FRACTION SEVEN EIGHTHS
+  {XK_trademark,                   0x2122},  // TRADE MARK SIGN
+  {XK_signaturemark,               0x2613},  // SALTIRE
+  {XK_leftopentriangle,            0x25C1},  // WHITE LEFT-POINTING TRIANGLE
+  {XK_rightopentriangle,           0x25B7},  // WHITE RIGHT-POINTING TRIANGLE
+  {XK_emopencircle,                0x25CB},  // WHITE CIRCLE
+  {XK_emopenrectangle,             0x25AF},  // WHITE VERTICAL RECTANGLE
+  {XK_leftsinglequotemark,         0x2018},  // LEFT SINGLE QUOTATION MARK
+  {XK_rightsinglequotemark,        0x2019},  // RIGHT SINGLE QUOTATION MARK
+  {XK_leftdoublequotemark,         0x201C},  // LEFT DOUBLE QUOTATION MARK
+  {XK_rightdoublequotemark,        0x201D},  // RIGHT DOUBLE QUOTATION MARK
+  {XK_prescription,                0x211E},  // PRESCRIPTION TAKE
+  {XK_minutes,                     0x2032},  // PRIME
+  {XK_seconds,                     0x2033},  // DOUBLE PRIME
+  {XK_latincross,                  0x271D},  // LATIN CROSS
+  {XK_filledrectbullet,            0x25AC},  // BLACK RECTANGLE
+  {XK_filledlefttribullet,         0x25C0},  // BLACK LEFT-POINTING TRIANGLE
+  {XK_filledrighttribullet,        0x25B6},  // BLACK RIGHT-POINTING TRIANGLE
+  {XK_emfilledcircle,              0x25CF},  // BLACK CIRCLE
+  {XK_emfilledrect,                0x25AE},  // BLACK VERTICAL RECTANGLE
+  {XK_enopencircbullet,            0x25E6},  // WHITE BULLET
+  {XK_enopensquarebullet,          0x25AB},  // WHITE SMALL SQUARE
+  {XK_openrectbullet,              0x25AD},  // WHITE RECTANGLE
+  {XK_opentribulletup,             0x25B3},  // WHITE UP-POINTING TRIANGLE
+  {XK_opentribulletdown,           0x25BD},  // WHITE DOWN-POINTING TRIANGLE
+  {XK_openstar,                    0x2606},  // WHITE STAR
+  {XK_enfilledcircbullet,          0x2022},  // BULLET
+  {XK_enfilledsqbullet,            0x25AA},  // BLACK SMALL SQUARE
+  {XK_filledtribulletup,           0x25B2},  // BLACK UP-POINTING TRIANGLE
+  {XK_filledtribulletdown,         0x25BC},  // BLACK DOWN-POINTING TRIANGLE
+  {XK_leftpointer,                 0x261C},  // WHITE LEFT POINTING INDEX
+  {XK_rightpointer,                0x261E},  // WHITE RIGHT POINTING INDEX
+  {XK_club,                        0x2663},  // BLACK CLUB SUIT
+  {XK_diamond,                     0x2666},  // BLACK DIAMOND SUIT
+  {XK_heart,                       0x2665},  // BLACK HEART SUIT
+  {XK_maltesecross,                0x2720},  // MALTESE CROSS
+  {XK_dagger,                      0x2020},  // DAGGER
+  {XK_doubledagger,                0x2021},  // DOUBLE DAGGER
+  {XK_checkmark,                   0x2713},  // CHECK MARK
+  {XK_ballotcross,                 0x2717},  // BALLOT X
+  {XK_musicalsharp,                0x266F},  // MUSIC SHARP SIGN
+  {XK_musicalflat,                 0x266D},  // MUSIC FLAT SIGN
+  {XK_malesymbol,                  0x2642},  // MALE SIGN
+  {XK_femalesymbol,                0x2640},  // FEMALE SIGN
+  {XK_telephone,                   0x260E},  // BLACK TELEPHONE
+  {XK_telephonerecorder,           0x2315},  // TELEPHONE RECORDER
+  {XK_phonographcopyright,         0x2117},  // SOUND RECORDING COPYRIGHT
+  {XK_caret,                       0x2038},  // CARET
+  {XK_singlelowquotemark,          0x201A},  // SINGLE LOW-9 QUOTATION MARK
+  {XK_doublelowquotemark,          0x201E},  // DOUBLE LOW-9 QUOTATION MARK
+
+  // APL
+  {XK_leftcaret,                   0x003C},  // LESS-THAN SIGN
+  {XK_rightcaret,                  0x003E},  // GREATER-THAN SIGN
+  {XK_downcaret,                   0x2228},  // LOGICAL OR
+  {XK_upcaret,                     0x2227},  // LOGICAL AND
+  {XK_overbar,                     0x00AF},  // MACRON
+  {XK_downtack,                    0x22A4},  // DOWN TACK
+  {XK_upshoe,                      0x2229},  // INTERSECTION
+  {XK_downstile,                   0x230A},  // LEFT FLOOR
+  {XK_underbar,                    0x005F},  // LOW LINE
+  {XK_jot,                         0x2218},  // RING OPERATOR
+  {XK_quad,                        0x2395},  // APL FUNCTIONAL SYMBOL QUAD
+  {XK_uptack,                      0x22A5},  // UP TACK
+  {XK_circle,                      0x25CB},  // WHITE CIRCLE
+  {XK_upstile,                     0x2308},  // LEFT CEILING
+  {XK_downshoe,                    0x222A},  // UNION
+  {XK_rightshoe,                   0x2283},  // SUPERSET OF
+  {XK_leftshoe,                    0x2282},  // SUBSET OF
+  {XK_lefttack,                    0x22A3},  // LEFT TACK
+  {XK_righttack,                   0x22A2},  // RIGHT TACK
+
+  // Hebrew
+  {XK_hebrew_doublelowline,        0x2017},  // DOUBLE LOW LINE
+  {XK_hebrew_aleph,                0x05D0},  // HEBREW LETTER ALEF
+  {XK_hebrew_bet,                  0x05D1},  // HEBREW LETTER BET
+  {XK_hebrew_gimel,                0x05D2},  // HEBREW LETTER GIMEL
+  {XK_hebrew_dalet,                0x05D3},  // HEBREW LETTER DALET
+  {XK_hebrew_he,                   0x05D4},  // HEBREW LETTER HE
+  {XK_hebrew_waw,                  0x05D5},  // HEBREW LETTER VAV
+  {XK_hebrew_zain,                 0x05D6},  // HEBREW LETTER ZAYIN
+  {XK_hebrew_chet,                 0x05D7},  // HEBREW LETTER HET
+  {XK_hebrew_tet,                  0x05D8},  // HEBREW LETTER TET
+  {XK_hebrew_yod,                  0x05D9},  // HEBREW LETTER YOD
+  {XK_hebrew_finalkaph,            0x05DA},  // HEBREW LETTER FINAL KAF
+  {XK_hebrew_kaph,                 0x05DB},  // HEBREW LETTER KAF
+  {XK_hebrew_lamed,                0x05DC},  // HEBREW LETTER LAMED
+  {XK_hebrew_finalmem,             0x05DD},  // HEBREW LETTER FINAL MEM
+  {XK_hebrew_mem,                  0x05DE},  // HEBREW LETTER MEM
+  {XK_hebrew_finalnun,             0x05DF},  // HEBREW LETTER FINAL NUN
+  {XK_hebrew_nun,                  0x05E0},  // HEBREW LETTER NUN
+  {XK_hebrew_samech,               0x05E1},  // HEBREW LETTER SAMEKH
+  {XK_hebrew_ayin,                 0x05E2},  // HEBREW LETTER AYIN
+  {XK_hebrew_finalpe,              0x05E3},  // HEBREW LETTER FINAL PE
+  {XK_hebrew_pe,                   0x05E4},  // HEBREW LETTER PE
+  {XK_hebrew_finalzade,            0x05E5},  // HEBREW LETTER FINAL TSADI
+  {XK_hebrew_zade,                 0x05E6},  // HEBREW LETTER TSADI
+  {XK_hebrew_qoph,                 0x05E7},  // HEBREW LETTER QOF
+  {XK_hebrew_resh,                 0x05E8},  // HEBREW LETTER RESH
+  {XK_hebrew_shin,                 0x05E9},  // HEBREW LETTER SHIN
+  {XK_hebrew_taw,                  0x05EA},  // HEBREW LETTER TAV
+
+  // Thai
+  {XK_Thai_kokai,                  0x0E01},  // THAI CHARACTER KO KAI
+  {XK_Thai_khokhai,                0x0E02},  // THAI CHARACTER KHO KHAI
+  {XK_Thai_khokhuat,               0x0E03},  // THAI CHARACTER KHO KHUAT
+  {XK_Thai_khokhwai,               0x0E04},  // THAI CHARACTER KHO KHWAI
+  {XK_Thai_khokhon,                0x0E05},  // THAI CHARACTER KHO KHON
+  {XK_Thai_khorakhang,             0x0E06},  // THAI CHARACTER KHO RAKHANG
+  {XK_Thai_ngongu,                 0x0E07},  // THAI CHARACTER NGO NGU
+  {XK_Thai_chochan,                0x0E08},  // THAI CHARACTER CHO CHAN
+  {XK_Thai_choching,               0x0E09},  // THAI CHARACTER CHO CHING
+  {XK_Thai_chochang,               0x0E0A},  // THAI CHARACTER CHO CHANG
+  {XK_Thai_soso,                   0x0E0B},  // THAI CHARACTER SO SO
+  {XK_Thai_chochoe,                0x0E0C},  // THAI CHARACTER CHO CHOE
+  {XK_Thai_yoying,                 0x0E0D},  // THAI CHARACTER YO YING
+  {XK_Thai_dochada,                0x0E0E},  // THAI CHARACTER DO CHADA
+  {XK_Thai_topatak,                0x0E0F},  // THAI CHARACTER TO PATAK
+  {XK_Thai_thothan,                0x0E10},  // THAI CHARACTER THO THAN
+  {XK_Thai_thonangmontho,          0x0E11},  // THAI CHARACTER THO NANGMONTHO
+  {XK_Thai_thophuthao,             0x0E12},  // THAI CHARACTER THO PHUTHAO
+  {XK_Thai_nonen,                  0x0E13},  // THAI CHARACTER NO NEN
+  {XK_Thai_dodek,                  0x0E14},  // THAI CHARACTER DO DEK
+  {XK_Thai_totao,                  0x0E15},  // THAI CHARACTER TO TAO
+  {XK_Thai_thothung,               0x0E16},  // THAI CHARACTER THO THUNG
+  {XK_Thai_thothahan,              0x0E17},  // THAI CHARACTER THO THAHAN
+  {XK_Thai_thothong,               0x0E18},  // THAI CHARACTER THO THONG
+  {XK_Thai_nonu,                   0x0E19},  // THAI CHARACTER NO NU
+  {XK_Thai_bobaimai,               0x0E1A},  // THAI CHARACTER BO BAIMAI
+  {XK_Thai_popla,                  0x0E1B},  // THAI CHARACTER PO PLA
+  {XK_Thai_phophung,               0x0E1C},  // THAI CHARACTER PHO PHUNG
+  {XK_Thai_fofa,                   0x0E1D},  // THAI CHARACTER FO FA
+  {XK_Thai_phophan,                0x0E1E},  // THAI CHARACTER PHO PHAN
+  {XK_Thai_fofan,                  0x0E1F},  // THAI CHARACTER FO FAN
+  {XK_Thai_phosamphao,             0x0E20},  // THAI CHARACTER PHO SAMPHAO
+  {XK_Thai_moma,                   0x0E21},  // THAI CHARACTER MO MA
+  {XK_Thai_yoyak,                  0x0E22},  // THAI CHARACTER YO YAK
+  {XK_Thai_rorua,                  0x0E23},  // THAI CHARACTER RO RUA
+  {XK_Thai_ru,                     0x0E24},  // THAI CHARACTER RU
+  {XK_Thai_loling,                 0x0E25},  // THAI CHARACTER LO LING
+  {XK_Thai_lu,                     0x0E26},  // THAI CHARACTER LU
+  {XK_Thai_wowaen,                 0x0E27},  // THAI CHARACTER WO WAEN
+  {XK_Thai_sosala,                 0x0E28},  // THAI CHARACTER SO SALA
+  {XK_Thai_sorusi,                 0x0E29},  // THAI CHARACTER SO RUSI
+  {XK_Thai_sosua,                  0x0E2A},  // THAI CHARACTER SO SUA
+  {XK_Thai_hohip,                  0x0E2B},  // THAI CHARACTER HO HIP
+  {XK_Thai_lochula,                0x0E2C},  // THAI CHARACTER LO CHULA
+  {XK_Thai_oang,                   0x0E2D},  // THAI CHARACTER O ANG
+  {XK_Thai_honokhuk,               0x0E2E},  // THAI CHARACTER HO NOKHUK
+  {XK_Thai_paiyannoi,              0x0E2F},  // THAI CHARACTER PAIYANNOI
+  {XK_Thai_saraa,                  0x0E30},  // THAI CHARACTER SARA A
+  {XK_Thai_maihanakat,             0x0E31},  // THAI CHARACTER MAI HAN-AKAT
+  {XK_Thai_saraaa,                 0x0E32},  // THAI CHARACTER SARA AA
+  {XK_Thai_saraam,                 0x0E33},  // THAI CHARACTER SARA AM
+  {XK_Thai_sarai,                  0x0E34},  // THAI CHARACTER SARA I
+  {XK_Thai_saraii,                 0x0E35},  // THAI CHARACTER SARA II
+  {XK_Thai_saraue,                 0x0E36},  // THAI CHARACTER SARA UE
+  {XK_Thai_sarauee,                0x0E37},  // THAI CHARACTER SARA UEE
+  {XK_Thai_sarau,                  0x0E38},  // THAI CHARACTER SARA U
+  {XK_Thai_sarauu,                 0x0E39},  // THAI CHARACTER SARA UU
+  {XK_Thai_phinthu,                0x0E3A},  // THAI CHARACTER PHINTHU
+  {XK_Thai_baht,                   0x0E3F},  // THAI CURRENCY SYMBOL BAHT
+  {XK_Thai_sarae,                  0x0E40},  // THAI CHARACTER SARA E
+  {XK_Thai_saraae,                 0x0E41},  // THAI CHARACTER SARA AE
+  {XK_Thai_sarao,                  0x0E42},  // THAI CHARACTER SARA O
+  {XK_Thai_saraaimaimuan,          0x0E43},  // THAI CHARACTER SARA AI MAIMUAN
+  {XK_Thai_saraaimaimalai,         0x0E44},  // THAI CHARACTER SARA AI MAIMALAI
+  {XK_Thai_lakkhangyao,            0x0E45},  // THAI CHARACTER LAKKHANGYAO
+  {XK_Thai_maiyamok,               0x0E46},  // THAI CHARACTER MAIYAMOK
+  {XK_Thai_maitaikhu,              0x0E47},  // THAI CHARACTER MAITAIKHU
+  {XK_Thai_maiek,                  0x0E48},  // THAI CHARACTER MAI EK
+  {XK_Thai_maitho,                 0x0E49},  // THAI CHARACTER MAI THO
+  {XK_Thai_maitri,                 0x0E4A},  // THAI CHARACTER MAI TRI
+  {XK_Thai_maichattawa,            0x0E4B},  // THAI CHARACTER MAI CHATTAWA
+  {XK_Thai_thanthakhat,            0x0E4C},  // THAI CHARACTER THANTHAKHAT
+  {XK_Thai_nikhahit,               0x0E4D},  // THAI CHARACTER NIKHAHIT
+  {XK_Thai_leksun,                 0x0E50},  // THAI DIGIT ZERO
+  {XK_Thai_leknung,                0x0E51},  // THAI DIGIT ONE
+  {XK_Thai_leksong,                0x0E52},  // THAI DIGIT TWO
+  {XK_Thai_leksam,                 0x0E53},  // THAI DIGIT THREE
+  {XK_Thai_leksi,                  0x0E54},  // THAI DIGIT FOUR
+  {XK_Thai_lekha,                  0x0E55},  // THAI DIGIT FIVE
+  {XK_Thai_lekhok,                 0x0E56},  // THAI DIGIT SIX
+  {XK_Thai_lekchet,                0x0E57},  // THAI DIGIT SEVEN
+  {XK_Thai_lekpaet,                0x0E58},  // THAI DIGIT EIGHT
+  {XK_Thai_lekkao,                 0x0E59},  // THAI DIGIT NINE
+
+  // Korean
+  {XK_Korean_Won,                  0x20A9},  // WON SIGN
+
+  // Armenian KeySyms map 1:1 to Unicode
+
+  // Georgian KeySyms map 1:1 to Unicode
+
+  // Azeri KeySyms map 1:1 to Unicode
+
+  // Vietnamese KeySyms map 1:1 to Unicode
+
+  // Currency KeySyms partially map 1:1 to Unicode
+  {XK_EuroSign,                    0x20AC},  // EURO SIGN
+
+  // Mathematical KeySyms map 1:1 to Unicode
+
+  // Braille KeySyms map 1:1 to Unicode
+
+  // Sinhala KeySyms map 1:1 to Unicode
+};
+
+class KeySymToUnicode {
+ public:
+  KeySymToUnicode()
+      : keysym_to_unicode_map_(arraysize(g_keysym_to_unicode_table)) {
+    for (size_t i = 0; i < arraysize(g_keysym_to_unicode_table); ++i) {
+      keysym_to_unicode_map_[g_keysym_to_unicode_table[i].keysym] =
+          g_keysym_to_unicode_table[i].unicode;
+    }
+  }
+
+  uint16_t UnicodeFromKeySym(KeySym keysym) const {
+    // Latin-1 characters have the same representation.
+    if ((0x0020 <= keysym && keysym <= 0x007e) ||
+        (0x00a0 <= keysym && keysym <= 0x00ff))
+      return static_cast<uint16_t>(keysym);
+
+    // Unicode-style KeySyms.
+    if ((keysym & 0xffe00000) == 0x01000000) {
+      uint32_t unicode = static_cast<uint32_t>(keysym & 0x1fffff);
+      if (unicode & ~0xffff)
+        return 0;  // We don't support characters outside the Basic Plane.
+      return static_cast<uint16_t>(unicode);
+    }
+
+    // Other KeySyms which are not Unicode-style.
+    KeySymToUnicodeMap::const_iterator i =
+        keysym_to_unicode_map_.find(keysym);
+    return i != keysym_to_unicode_map_.end() ? i->second : 0;
+  }
+
+ private:
+  typedef std::unordered_map<KeySym, uint16_t> KeySymToUnicodeMap;
+  KeySymToUnicodeMap keysym_to_unicode_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(KeySymToUnicode);
+};
+
+static base::LazyInstance<KeySymToUnicode>::Leaky g_keysym_to_unicode =
+    LAZY_INSTANCE_INITIALIZER;
+
+uint16_t GetUnicodeCharacterFromXKeySym(unsigned long keysym) {
+  return g_keysym_to_unicode.Get().UnicodeFromKeySym(
+      static_cast<KeySym>(keysym));
+}
+
+}  // namespace ui
diff --git a/ui/events/x/keysym_to_unicode.h b/ui/events/x/keysym_to_unicode.h
new file mode 100644
index 0000000..ba530b6
--- /dev/null
+++ b/ui/events/x/keysym_to_unicode.h
@@ -0,0 +1,20 @@
+// Copyright 2014 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 UI_EVENTS_X_KEYSYM_TO_UNICODE_H_
+#define UI_EVENTS_X_KEYSYM_TO_UNICODE_H_
+
+#include <cstdint>
+
+namespace ui {
+
+// Returns a Unicode character corresponding to the given |keysym|.  If the
+// |keysym| doesn't represent a printable character, returns zero.  We don't
+// support characters outside the Basic Plane, and this function returns zero
+// in that case.
+uint16_t GetUnicodeCharacterFromXKeySym(unsigned long keysym);
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_KEYSYM_TO_UNICODE_H_
diff --git a/ui/events/x/touch_factory_x11.cc b/ui/events/x/touch_factory_x11.cc
new file mode 100644
index 0000000..10cb890
--- /dev/null
+++ b/ui/events/x/touch_factory_x11.cc
@@ -0,0 +1,355 @@
+// Copyright (c) 2012 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 "ui/events/x/touch_factory_x11.h"
+
+#include <X11/Xatom.h>
+#include <X11/cursorfont.h>
+#include <X11/extensions/XInput.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/extensions/XIproto.h>
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/sys_info.h"
+#include "ui/events/event_switches.h"
+#include "ui/events/x/device_data_manager_x11.h"
+#include "ui/events/x/device_list_cache_x.h"
+#include "ui/gfx/x/x11_types.h"
+
+namespace ui {
+
+TouchFactory::TouchFactory()
+    : pointer_device_lookup_(),
+      touch_events_disabled_(false),
+      touch_device_list_(),
+      max_touch_points_(-1),
+      virtual_core_keyboard_device_(-1),
+      id_generator_(0) {
+  if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available())
+    return;
+
+  XDisplay* display = gfx::GetXDisplay();
+  UpdateDeviceList(display);
+
+  CommandLine* cmdline = CommandLine::ForCurrentProcess();
+  touch_events_disabled_ = cmdline->HasSwitch(switches::kTouchEvents) &&
+      cmdline->GetSwitchValueASCII(switches::kTouchEvents) ==
+          switches::kTouchEventsDisabled;
+}
+
+TouchFactory::~TouchFactory() {
+}
+
+// static
+TouchFactory* TouchFactory::GetInstance() {
+  return Singleton<TouchFactory>::get();
+}
+
+// static
+void TouchFactory::SetTouchDeviceListFromCommandLine() {
+  // Get a list of pointer-devices that should be treated as touch-devices.
+  // This is primarily used for testing/debugging touch-event processing when a
+  // touch-device isn't available.
+  std::string touch_devices =
+      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kTouchDevices);
+
+  if (!touch_devices.empty()) {
+    std::vector<std::string> devs;
+    std::vector<unsigned int> device_ids;
+    unsigned int devid;
+    base::SplitString(touch_devices, ',', &devs);
+    for (std::vector<std::string>::iterator iter = devs.begin();
+        iter != devs.end(); ++iter) {
+      if (base::StringToInt(*iter, reinterpret_cast<int*>(&devid)))
+        device_ids.push_back(devid);
+      else
+        DLOG(WARNING) << "Invalid touch-device id: " << *iter;
+    }
+    ui::TouchFactory::GetInstance()->SetTouchDeviceList(device_ids);
+  }
+}
+
+void TouchFactory::UpdateDeviceList(Display* display) {
+  // Detect touch devices.
+  touch_device_lookup_.reset();
+  touch_device_list_.clear();
+  touchscreen_ids_.clear();
+  max_touch_points_ = -1;
+
+#if !defined(USE_XI2_MT)
+  // NOTE: The new API for retrieving the list of devices (XIQueryDevice) does
+  // not provide enough information to detect a touch device. As a result, the
+  // old version of query function (XListInputDevices) is used instead.
+  // If XInput2 is not supported, this will return null (with count of -1) so
+  // we assume there cannot be any touch devices.
+  // With XI2.1 or older, we allow only single touch devices.
+  XDeviceList dev_list =
+      DeviceListCacheX::GetInstance()->GetXDeviceList(display);
+  Atom xi_touchscreen = XInternAtom(display, XI_TOUCHSCREEN, false);
+  for (int i = 0; i < dev_list.count; i++) {
+    if (dev_list[i].type == xi_touchscreen) {
+      touch_device_lookup_[dev_list[i].id] = true;
+      touch_device_list_[dev_list[i].id] = false;
+    }
+  }
+#endif
+
+  if (!DeviceDataManagerX11::GetInstance()->IsXInput2Available())
+    return;
+
+  // Instead of asking X for the list of devices all the time, let's maintain a
+  // list of pointer devices we care about.
+  // It should not be necessary to select for slave devices. XInput2 provides
+  // enough information to the event callback to decide which slave device
+  // triggered the event, thus decide whether the 'pointer event' is a
+  // 'mouse event' or a 'touch event'.
+  // However, on some desktops, some events from a master pointer are
+  // not delivered to the client. So we select for slave devices instead.
+  // If the touch device has 'GrabDevice' set and 'SendCoreEvents' unset (which
+  // is possible), then the device is detected as a floating device, and a
+  // floating device is not connected to a master device. So it is necessary to
+  // also select on the floating devices.
+  pointer_device_lookup_.reset();
+  XIDeviceList xi_dev_list =
+      DeviceListCacheX::GetInstance()->GetXI2DeviceList(display);
+  for (int i = 0; i < xi_dev_list.count; i++) {
+    XIDeviceInfo* devinfo = xi_dev_list.devices + i;
+    if (devinfo->use == XIFloatingSlave || devinfo->use == XIMasterPointer) {
+#if defined(USE_XI2_MT)
+      for (int k = 0; k < devinfo->num_classes; ++k) {
+        XIAnyClassInfo* xiclassinfo = devinfo->classes[k];
+        if (xiclassinfo->type == XITouchClass) {
+          XITouchClassInfo* tci =
+              reinterpret_cast<XITouchClassInfo*>(xiclassinfo);
+          // Only care direct touch device (such as touch screen) right now
+          if (tci->mode == XIDirectTouch) {
+            touch_device_lookup_[devinfo->deviceid] = true;
+            touch_device_list_[devinfo->deviceid] = true;
+            if (tci->num_touches > 0 && tci->num_touches > max_touch_points_)
+              max_touch_points_ = tci->num_touches;
+          }
+        }
+      }
+#endif
+      pointer_device_lookup_[devinfo->deviceid] = true;
+    } else if (devinfo->use == XIMasterKeyboard) {
+      virtual_core_keyboard_device_ = devinfo->deviceid;
+    }
+
+#if defined(USE_XI2_MT)
+    if (devinfo->use == XIFloatingSlave || devinfo->use == XISlavePointer) {
+      for (int k = 0; k < devinfo->num_classes; ++k) {
+        XIAnyClassInfo* xiclassinfo = devinfo->classes[k];
+        if (xiclassinfo->type == XITouchClass) {
+          XITouchClassInfo* tci =
+              reinterpret_cast<XITouchClassInfo*>(xiclassinfo);
+          // Only care direct touch device (such as touch screen) right now
+          if (tci->mode == XIDirectTouch)
+            CacheTouchscreenIds(display, devinfo->deviceid);
+        }
+      }
+    }
+#endif
+  }
+}
+
+bool TouchFactory::ShouldProcessXI2Event(XEvent* xev) {
+  DCHECK_EQ(GenericEvent, xev->type);
+  XIEvent* event = static_cast<XIEvent*>(xev->xcookie.data);
+  XIDeviceEvent* xiev = reinterpret_cast<XIDeviceEvent*>(event);
+
+#if defined(USE_XI2_MT)
+  if (event->evtype == XI_TouchBegin ||
+      event->evtype == XI_TouchUpdate ||
+      event->evtype == XI_TouchEnd) {
+    return !touch_events_disabled_ && IsTouchDevice(xiev->deviceid);
+  }
+#endif
+  // Make sure only key-events from the virtual core keyboard are processed.
+  if (event->evtype == XI_KeyPress || event->evtype == XI_KeyRelease) {
+    return (virtual_core_keyboard_device_ < 0) ||
+           (virtual_core_keyboard_device_ == xiev->deviceid);
+  }
+
+  if (event->evtype != XI_ButtonPress &&
+      event->evtype != XI_ButtonRelease &&
+      event->evtype != XI_Motion)
+    return true;
+
+  if (!pointer_device_lookup_[xiev->deviceid])
+    return false;
+
+  return IsTouchDevice(xiev->deviceid) ? !touch_events_disabled_ : true;
+}
+
+void TouchFactory::SetupXI2ForXWindow(Window window) {
+  // Setup mask for mouse events. It is possible that a device is loaded/plugged
+  // in after we have setup XInput2 on a window. In such cases, we need to
+  // either resetup XInput2 for the window, so that we get events from the new
+  // device, or we need to listen to events from all devices, and then filter
+  // the events from uninteresting devices. We do the latter because that's
+  // simpler.
+
+  XDisplay* display = gfx::GetXDisplay();
+
+  unsigned char mask[XIMaskLen(XI_LASTEVENT)];
+  memset(mask, 0, sizeof(mask));
+
+#if defined(USE_XI2_MT)
+  XISetMask(mask, XI_TouchBegin);
+  XISetMask(mask, XI_TouchUpdate);
+  XISetMask(mask, XI_TouchEnd);
+#endif
+  XISetMask(mask, XI_ButtonPress);
+  XISetMask(mask, XI_ButtonRelease);
+  XISetMask(mask, XI_Motion);
+#if defined(OS_CHROMEOS)
+  if (base::SysInfo::IsRunningOnChromeOS()) {
+    XISetMask(mask, XI_KeyPress);
+    XISetMask(mask, XI_KeyRelease);
+  }
+#endif
+
+  XIEventMask evmask;
+  evmask.deviceid = XIAllDevices;
+  evmask.mask_len = sizeof(mask);
+  evmask.mask = mask;
+  XISelectEvents(display, window, &evmask, 1);
+  XFlush(display);
+}
+
+void TouchFactory::SetTouchDeviceList(
+    const std::vector<unsigned int>& devices) {
+  touch_device_lookup_.reset();
+  touch_device_list_.clear();
+  for (std::vector<unsigned int>::const_iterator iter = devices.begin();
+       iter != devices.end(); ++iter) {
+    DCHECK(*iter < touch_device_lookup_.size());
+    touch_device_lookup_[*iter] = true;
+    touch_device_list_[*iter] = false;
+  }
+}
+
+bool TouchFactory::IsTouchDevice(unsigned deviceid) const {
+  return deviceid < touch_device_lookup_.size() ?
+      touch_device_lookup_[deviceid] : false;
+}
+
+bool TouchFactory::IsMultiTouchDevice(unsigned int deviceid) const {
+  return (deviceid < touch_device_lookup_.size() &&
+          touch_device_lookup_[deviceid]) ?
+          touch_device_list_.find(deviceid)->second :
+          false;
+}
+
+bool TouchFactory::QuerySlotForTrackingID(uint32 tracking_id, int* slot) {
+  if (!id_generator_.HasGeneratedIDFor(tracking_id))
+    return false;
+  *slot = static_cast<int>(id_generator_.GetGeneratedID(tracking_id));
+  return true;
+}
+
+int TouchFactory::GetSlotForTrackingID(uint32 tracking_id) {
+  return id_generator_.GetGeneratedID(tracking_id);
+}
+
+void TouchFactory::AcquireSlotForTrackingID(uint32 tracking_id) {
+  tracking_id_refcounts_[tracking_id]++;
+}
+
+void TouchFactory::ReleaseSlotForTrackingID(uint32 tracking_id) {
+  tracking_id_refcounts_[tracking_id]--;
+  if (tracking_id_refcounts_[tracking_id] == 0)
+    id_generator_.ReleaseNumber(tracking_id);
+}
+
+bool TouchFactory::IsTouchDevicePresent() {
+  return !touch_events_disabled_ && touch_device_lookup_.any();
+}
+
+int TouchFactory::GetMaxTouchPoints() const {
+  return max_touch_points_;
+}
+
+void TouchFactory::ResetForTest() {
+  pointer_device_lookup_.reset();
+  touch_device_lookup_.reset();
+  touch_events_disabled_ = false;
+  touch_device_list_.clear();
+  touchscreen_ids_.clear();
+  tracking_id_refcounts_.clear();
+  max_touch_points_ = -1;
+  id_generator_.ResetForTest();
+}
+
+void TouchFactory::SetTouchDeviceForTest(
+    const std::vector<unsigned int>& devices) {
+  touch_device_lookup_.reset();
+  touch_device_list_.clear();
+  for (std::vector<unsigned int>::const_iterator iter = devices.begin();
+       iter != devices.end(); ++iter) {
+    DCHECK(*iter < touch_device_lookup_.size());
+    touch_device_lookup_[*iter] = true;
+    touch_device_list_[*iter] = true;
+  }
+  touch_events_disabled_ = false;
+}
+
+void TouchFactory::SetPointerDeviceForTest(
+    const std::vector<unsigned int>& devices) {
+  pointer_device_lookup_.reset();
+  for (std::vector<unsigned int>::const_iterator iter = devices.begin();
+       iter != devices.end(); ++iter) {
+    pointer_device_lookup_[*iter] = true;
+  }
+}
+
+void TouchFactory::CacheTouchscreenIds(Display* display, int device_id) {
+  XDevice* device = XOpenDevice(display, device_id);
+  if (!device)
+    return;
+
+  Atom actual_type_return;
+  int actual_format_return;
+  unsigned long nitems_return;
+  unsigned long bytes_after_return;
+  unsigned char *prop_return;
+
+  const char kDeviceProductIdString[] = "Device Product ID";
+  Atom device_product_id_atom =
+      XInternAtom(display, kDeviceProductIdString, false);
+
+  if (device_product_id_atom != None &&
+      XGetDeviceProperty(display, device, device_product_id_atom, 0, 2,
+                         False, XA_INTEGER, &actual_type_return,
+                         &actual_format_return, &nitems_return,
+                         &bytes_after_return, &prop_return) == Success) {
+    if (actual_type_return == XA_INTEGER &&
+        actual_format_return == 32 &&
+        nitems_return == 2) {
+      // An actual_format_return of 32 implies that the returned data is an
+      // array of longs. See the description of |prop_return| in `man
+      // XGetDeviceProperty` for details.
+      long* ptr = reinterpret_cast<long*>(prop_return);
+
+      // Internal displays will have a vid and pid of 0. Ignore them.
+      // ptr[0] is the vid, and ptr[1] is the pid.
+      if (ptr[0] || ptr[1])
+        touchscreen_ids_.insert(std::make_pair(ptr[0], ptr[1]));
+    }
+    XFree(prop_return);
+  }
+
+  XCloseDevice(display, device);
+}
+
+}  // namespace ui
diff --git a/ui/events/x/touch_factory_x11.h b/ui/events/x/touch_factory_x11.h
new file mode 100644
index 0000000..183dc09
--- /dev/null
+++ b/ui/events/x/touch_factory_x11.h
@@ -0,0 +1,157 @@
+// Copyright (c) 2012 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 UI_EVENTS_X_TOUCH_FACTORY_X11_H_
+#define UI_EVENTS_X_TOUCH_FACTORY_X11_H_
+
+#include <bitset>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/timer/timer.h"
+#include "ui/events/events_base_export.h"
+#include "ui/gfx/sequential_id_generator.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+typedef unsigned long Cursor;
+typedef unsigned long Window;
+typedef struct _XDisplay Display;
+typedef union _XEvent XEvent;
+
+namespace ui {
+
+// Functions related to determining touch devices.
+class EVENTS_BASE_EXPORT TouchFactory {
+ private:
+  TouchFactory();
+  ~TouchFactory();
+
+ public:
+  // Returns the TouchFactory singleton.
+  static TouchFactory* GetInstance();
+
+  // Sets the touch devices from the command line.
+  static void SetTouchDeviceListFromCommandLine();
+
+  // Updates the list of devices.
+  void UpdateDeviceList(Display* display);
+
+  // Checks whether an XI2 event should be processed or not (i.e. if the event
+  // originated from a device we are interested in).
+  bool ShouldProcessXI2Event(XEvent* xevent);
+
+  // Setup an X Window for XInput2 events.
+  void SetupXI2ForXWindow(::Window xid);
+
+  // Keeps a list of touch devices so that it is possible to determine if a
+  // pointer event is a touch-event or a mouse-event. The list is reset each
+  // time this is called.
+  void SetTouchDeviceList(const std::vector<unsigned int>& devices);
+
+  // Is the device a touch-device?
+  bool IsTouchDevice(unsigned int deviceid) const;
+
+  // Is the device a real multi-touch-device? (see doc. for |touch_device_list_|
+  // below for more explanation.)
+  bool IsMultiTouchDevice(unsigned int deviceid) const;
+
+  // Tries to find an existing slot ID mapping to tracking ID. Returns true
+  // if the slot is found and it is saved in |slot|, false if no such slot
+  // can be found.
+  bool QuerySlotForTrackingID(uint32 tracking_id, int* slot);
+
+  // Tries to find an existing slot ID mapping to tracking ID. If there
+  // isn't one already, allocates a new slot ID and sets up the mapping.
+  int GetSlotForTrackingID(uint32 tracking_id);
+
+  // Increases the number of times |ReleaseSlotForTrackingID| needs to be called
+  // on a given tracking id before it will actually be released.
+  void AcquireSlotForTrackingID(uint32 tracking_id);
+
+  // Releases the slot ID mapping to tracking ID.
+  void ReleaseSlotForTrackingID(uint32 tracking_id);
+
+  // Whether any touch device is currently present and enabled.
+  bool IsTouchDevicePresent();
+
+  // Pairs of <vendor id, product id> of external touch screens.
+  const std::set<std::pair<int, int> >& GetTouchscreenIds() const {
+    return touchscreen_ids_;
+  }
+
+  // Return maximum simultaneous touch points supported by device.
+  int GetMaxTouchPoints() const;
+
+  // Resets the TouchFactory singleton.
+  void ResetForTest();
+
+  // Sets up the device id in the list |devices| as multi-touch capable
+  // devices and enables touch events processing. This function is only
+  // for test purpose, and it does not query from X server.
+  void SetTouchDeviceForTest(const std::vector<unsigned int>& devices);
+
+  // Sets up the device id in the list |devices| as pointer devices.
+  // This function is only for test purpose, and it does not query from
+  // X server.
+  void SetPointerDeviceForTest(const std::vector<unsigned int>& devices);
+
+ private:
+  // Requirement for Singleton
+  friend struct DefaultSingletonTraits<TouchFactory>;
+
+  void CacheTouchscreenIds(Display* display, int id);
+
+  // NOTE: To keep track of touch devices, we currently maintain a lookup table
+  // to quickly decide if a device is a touch device or not. We also maintain a
+  // list of the touch devices. Ideally, there will be only one touch device,
+  // and instead of having the lookup table and the list, there will be a single
+  // identifier for the touch device. This can be completed after enough testing
+  // on real touch devices.
+
+  static const int kMaxDeviceNum = 128;
+
+  // A quick lookup table for determining if events from the pointer device
+  // should be processed.
+  std::bitset<kMaxDeviceNum> pointer_device_lookup_;
+
+  // A quick lookup table for determining if a device is a touch device.
+  std::bitset<kMaxDeviceNum> touch_device_lookup_;
+
+  // Indicates whether touch events are explicitly disabled.
+  bool touch_events_disabled_;
+
+  // The list of touch devices. For testing/debugging purposes, a single-pointer
+  // device (mouse or touch screen without sufficient X/driver support for MT)
+  // can sometimes be treated as a touch device. The key in the map represents
+  // the device id, and the value represents if the device is multi-touch
+  // capable.
+  std::map<int, bool> touch_device_list_;
+
+  // Touch screen <vid, pid>s.
+  std::set<std::pair<int, int> > touchscreen_ids_;
+
+  // Maps from a tracking id to the number of times |ReleaseSlotForTrackingID|
+  // must be called before the tracking id is released.
+  std::map<uint32, int> tracking_id_refcounts_;
+
+  // Maximum simultaneous touch points supported by device. In the case of
+  // devices with multiple digitizers (e.g. multiple touchscreens), the value
+  // is the maximum of the set of maximum supported contacts by each individual
+  // digitizer.
+  int max_touch_points_;
+
+  // Device ID of the virtual core keyboard.
+  int virtual_core_keyboard_device_;
+
+  SequentialIDGenerator id_generator_;
+
+  DISALLOW_COPY_AND_ASSIGN(TouchFactory);
+};
+
+}  // namespace ui
+
+#endif  // UI_EVENTS_X_TOUCH_FACTORY_X11_H_