summaryrefslogtreecommitdiff
path: root/chromium/ui/events
diff options
context:
space:
mode:
authorJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-08 14:30:41 +0200
committerJocelyn Turcotte <jocelyn.turcotte@digia.com>2014-08-12 13:49:54 +0200
commitab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch)
tree498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/ui/events
parent4ce69f7403811819800e7c5ae1318b2647e778d1 (diff)
downloadqtwebengine-chromium-ab0a50979b9eb4dfa3320eff7e187e41efedf7a9.tar.gz
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/ui/events')
-rw-r--r--chromium/ui/events/BUILD.gn311
-rw-r--r--chromium/ui/events/PRESUBMIT.py26
-rw-r--r--chromium/ui/events/cocoa/cocoa_event_utils.h26
-rw-r--r--chromium/ui/events/cocoa/cocoa_event_utils.mm55
-rw-r--r--chromium/ui/events/cocoa/events_mac.mm261
-rw-r--r--chromium/ui/events/cocoa/events_mac_unittest.mm315
-rw-r--r--chromium/ui/events/event.cc264
-rw-r--r--chromium/ui/events/event.h144
-rw-r--r--chromium/ui/events/event_constants.h46
-rw-r--r--chromium/ui/events/event_dispatcher.cc28
-rw-r--r--chromium/ui/events/event_dispatcher.h4
-rw-r--r--chromium/ui/events/event_dispatcher_unittest.cc62
-rw-r--r--chromium/ui/events/event_processor.cc16
-rw-r--r--chromium/ui/events/event_processor.h4
-rw-r--r--chromium/ui/events/event_processor_unittest.cc219
-rw-r--r--chromium/ui/events/event_rewriter.h68
-rw-r--r--chromium/ui/events/event_rewriter_unittest.cc231
-rw-r--r--chromium/ui/events/event_source.cc65
-rw-r--r--chromium/ui/events/event_source.h24
-rw-r--r--chromium/ui/events/event_switches.cc17
-rw-r--r--chromium/ui/events/event_switches.h8
-rw-r--r--chromium/ui/events/event_target.h4
-rw-r--r--chromium/ui/events/event_targeter.cc24
-rw-r--r--chromium/ui/events/event_targeter.h37
-rw-r--r--chromium/ui/events/event_unittest.cc132
-rw-r--r--chromium/ui/events/event_utils.cc63
-rw-r--r--chromium/ui/events/event_utils.h44
-rw-r--r--chromium/ui/events/events.gyp221
-rw-r--r--chromium/ui/events/events_stub.cc29
-rw-r--r--chromium/ui/events/gesture_detection/OWNERS2
-rw-r--r--chromium/ui/events/gesture_detection/bitset_32.h128
-rw-r--r--chromium/ui/events/gesture_detection/bitset_32_unittest.cc100
-rw-r--r--chromium/ui/events/gesture_detection/filtered_gesture_provider.cc77
-rw-r--r--chromium/ui/events/gesture_detection/filtered_gesture_provider.h61
-rw-r--r--chromium/ui/events/gesture_detection/gesture_config_helper.cc17
-rw-r--r--chromium/ui/events/gesture_detection/gesture_config_helper.h20
-rw-r--r--chromium/ui/events/gesture_detection/gesture_config_helper_android.cc73
-rw-r--r--chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc73
-rw-r--r--chromium/ui/events/gesture_detection/gesture_detection_export.h29
-rw-r--r--chromium/ui/events/gesture_detection/gesture_detector.cc553
-rw-r--r--chromium/ui/events/gesture_detection/gesture_detector.h214
-rw-r--r--chromium/ui/events/gesture_detection/gesture_event_data.cc50
-rw-r--r--chromium/ui/events/gesture_detection/gesture_event_data.h48
-rw-r--r--chromium/ui/events/gesture_detection/gesture_event_data_packet.cc96
-rw-r--r--chromium/ui/events/gesture_detection/gesture_event_data_packet.h68
-rw-r--r--chromium/ui/events/gesture_detection/gesture_provider.cc810
-rw-r--r--chromium/ui/events/gesture_detection/gesture_provider.h136
-rw-r--r--chromium/ui/events/gesture_detection/gesture_provider_unittest.cc2298
-rw-r--r--chromium/ui/events/gesture_detection/mock_motion_event.cc204
-rw-r--r--chromium/ui/events/gesture_detection/mock_motion_event.h86
-rw-r--r--chromium/ui/events/gesture_detection/motion_event.h73
-rw-r--r--chromium/ui/events/gesture_detection/scale_gesture_detector.cc383
-rw-r--r--chromium/ui/events/gesture_detection/scale_gesture_detector.h158
-rw-r--r--chromium/ui/events/gesture_detection/snap_scroll_controller.cc106
-rw-r--r--chromium/ui/events/gesture_detection/snap_scroll_controller.h60
-rw-r--r--chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc400
-rw-r--r--chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h108
-rw-r--r--chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc1059
-rw-r--r--chromium/ui/events/gesture_detection/velocity_tracker.cc805
-rw-r--r--chromium/ui/events/gesture_detection/velocity_tracker.h150
-rw-r--r--chromium/ui/events/gesture_detection/velocity_tracker_state.cc105
-rw-r--r--chromium/ui/events/gesture_detection/velocity_tracker_state.h50
-rw-r--r--chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc193
-rw-r--r--chromium/ui/events/gesture_event_details.cc71
-rw-r--r--chromium/ui/events/gesture_event_details.h171
-rw-r--r--chromium/ui/events/gestures/OWNERS1
-rw-r--r--chromium/ui/events/gestures/gesture_configuration.cc17
-rw-r--r--chromium/ui/events/gestures/gesture_configuration.h29
-rw-r--r--chromium/ui/events/gestures/gesture_point.cc85
-rw-r--r--chromium/ui/events/gestures/gesture_point.h47
-rw-r--r--chromium/ui/events/gestures/gesture_provider_aura.cc142
-rw-r--r--chromium/ui/events/gestures/gesture_provider_aura.h59
-rw-r--r--chromium/ui/events/gestures/gesture_recognizer.h24
-rw-r--r--chromium/ui/events/gestures/gesture_recognizer_impl.cc216
-rw-r--r--chromium/ui/events/gestures/gesture_recognizer_impl.h29
-rw-r--r--chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc64
-rw-r--r--chromium/ui/events/gestures/gesture_sequence.cc268
-rw-r--r--chromium/ui/events/gestures/gesture_sequence.h35
-rw-r--r--chromium/ui/events/gestures/gesture_types.cc105
-rw-r--r--chromium/ui/events/gestures/gesture_types.h167
-rw-r--r--chromium/ui/events/gestures/gesture_util.cc23
-rw-r--r--chromium/ui/events/gestures/gesture_util.h26
-rw-r--r--chromium/ui/events/gestures/gestures.dot5
-rw-r--r--chromium/ui/events/gestures/motion_event_aura.cc255
-rw-r--r--chromium/ui/events/gestures/motion_event_aura.h106
-rw-r--r--chromium/ui/events/gestures/motion_event_aura_unittest.cc323
-rw-r--r--chromium/ui/events/gestures/unified_gesture_detector_enabled.cc37
-rw-r--r--chromium/ui/events/gestures/unified_gesture_detector_enabled.h17
-rw-r--r--chromium/ui/events/gestures/velocity_calculator.cc2
-rw-r--r--chromium/ui/events/gestures/velocity_calculator.h6
-rw-r--r--chromium/ui/events/gestures/velocity_calculator_unittest.cc2
-rw-r--r--chromium/ui/events/ipc/BUILD.gn17
-rw-r--r--chromium/ui/events/ipc/OWNERS13
-rw-r--r--chromium/ui/events/ipc/events_ipc.gyp29
-rw-r--r--chromium/ui/events/ipc/latency_info_param_traits.cc26
-rw-r--r--chromium/ui/events/ipc/latency_info_param_traits.h30
-rw-r--r--chromium/ui/events/keycodes/DEPS11
-rw-r--r--chromium/ui/events/keycodes/dom4/DEPS3
-rw-r--r--chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc85
-rw-r--r--chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h58
-rw-r--r--chromium/ui/events/keycodes/keyboard_code_conversion_mac.h4
-rw-r--r--chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm12
-rw-r--r--chromium/ui/events/keycodes/keyboard_code_conversion_x.cc899
-rw-r--r--chromium/ui/events/keycodes/keyboard_code_conversion_x.h5
-rw-r--r--chromium/ui/events/latency_info.cc51
-rw-r--r--chromium/ui/events/latency_info.h35
-rw-r--r--chromium/ui/events/latency_info_nacl.gyp78
-rw-r--r--chromium/ui/events/latency_info_unittest.cc80
-rw-r--r--chromium/ui/events/linux/text_edit_command_auralinux.cc124
-rw-r--r--chromium/ui/events/linux/text_edit_command_auralinux.h82
-rw-r--r--chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc23
-rw-r--r--chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h43
-rw-r--r--chromium/ui/events/ozone/BUILD.gn101
-rw-r--r--chromium/ui/events/ozone/DEPS4
-rw-r--r--chromium/ui/events/ozone/OWNERS1
-rw-r--r--chromium/ui/events/ozone/device/device_event.cc16
-rw-r--r--chromium/ui/events/ozone/device/device_event.h43
-rw-r--r--chromium/ui/events/ozone/device/device_event_observer.h24
-rw-r--r--chromium/ui/events/ozone/device/device_manager.cc23
-rw-r--r--chromium/ui/events/ozone/device/device_manager.h36
-rw-r--r--chromium/ui/events/ozone/device/device_manager_manual.cc33
-rw-r--r--chromium/ui/events/ozone/device/device_manager_manual.h29
-rw-r--r--chromium/ui/events/ozone/device/udev/device_manager_udev.cc186
-rw-r--r--chromium/ui/events/ozone/device/udev/device_manager_udev.h51
-rw-r--r--chromium/ui/events/ozone/evdev/cursor_delegate_evdev.h31
-rw-r--r--chromium/ui/events/ozone/evdev/event_converter_evdev.cc23
-rw-r--r--chromium/ui/events/ozone/evdev/event_converter_evdev.h46
-rw-r--r--chromium/ui/events/ozone/evdev/event_device_info.cc63
-rw-r--r--chromium/ui/events/ozone/evdev/event_device_info.h19
-rw-r--r--chromium/ui/events/ozone/evdev/event_factory.cc83
-rw-r--r--chromium/ui/events/ozone/evdev/event_factory.h31
-rw-r--r--chromium/ui/events/ozone/evdev/event_factory_evdev.cc242
-rw-r--r--chromium/ui/events/ozone/evdev/event_factory_evdev.h82
-rw-r--r--chromium/ui/events/ozone/evdev/event_modifiers_evdev.cc (renamed from chromium/ui/events/ozone/evdev/event_modifiers.cc)9
-rw-r--r--chromium/ui/events/ozone/evdev/event_modifiers_evdev.h (renamed from chromium/ui/events/ozone/evdev/event_modifiers.h)17
-rw-r--r--chromium/ui/events/ozone/evdev/events_ozone_evdev_export.h29
-rw-r--r--chromium/ui/events/ozone/evdev/key_event_converter.h46
-rw-r--r--chromium/ui/events/ozone/evdev/key_event_converter_evdev.cc (renamed from chromium/ui/events/ozone/evdev/key_event_converter.cc)49
-rw-r--r--chromium/ui/events/ozone/evdev/key_event_converter_evdev.h60
-rw-r--r--chromium/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc (renamed from chromium/ui/events/ozone/evdev/key_event_converter_unittest.cc)48
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc107
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h78
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc274
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h82
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc32
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h15
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc72
-rw-r--r--chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h16
-rw-r--r--chromium/ui/events/ozone/evdev/touch_event_converter.cc197
-rw-r--r--chromium/ui/events/ozone/evdev/touch_event_converter.h82
-rw-r--r--chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc307
-rw-r--r--chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h118
-rw-r--r--chromium/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc (renamed from chromium/ui/events/ozone/evdev/touch_event_converter_unittest.cc)121
-rw-r--r--chromium/ui/events/ozone/event_converter_ozone.cc33
-rw-r--r--chromium/ui/events/ozone/event_converter_ozone.h40
-rw-r--r--chromium/ui/events/ozone/event_factory_ozone.cc61
-rw-r--r--chromium/ui/events/ozone/event_factory_ozone.h62
-rw-r--r--chromium/ui/events/ozone/events_ozone.cc24
-rw-r--r--chromium/ui/events/ozone/events_ozone.gyp97
-rw-r--r--chromium/ui/events/ozone/events_ozone_export.h29
-rw-r--r--chromium/ui/events/platform/BUILD.gn32
-rw-r--r--chromium/ui/events/platform/events_platform.gyp36
-rw-r--r--chromium/ui/events/platform/platform_event_dispatcher.h43
-rw-r--r--chromium/ui/events/platform/platform_event_observer.h29
-rw-r--r--chromium/ui/events/platform/platform_event_source.cc111
-rw-r--r--chromium/ui/events/platform/platform_event_source.h102
-rw-r--r--chromium/ui/events/platform/platform_event_source_stub.cc13
-rw-r--r--chromium/ui/events/platform/platform_event_source_unittest.cc787
-rw-r--r--chromium/ui/events/platform/platform_event_types.h14
-rw-r--r--chromium/ui/events/platform/scoped_event_dispatcher.cc21
-rw-r--r--chromium/ui/events/platform/scoped_event_dispatcher.h40
-rw-r--r--chromium/ui/events/platform/x11/BUILD.gn42
-rw-r--r--chromium/ui/events/platform/x11/x11_event_source.cc149
-rw-r--r--chromium/ui/events/platform/x11/x11_event_source.h64
-rw-r--r--chromium/ui/events/platform/x11/x11_event_source_glib.cc101
-rw-r--r--chromium/ui/events/platform/x11/x11_event_source_libevent.cc68
-rw-r--r--chromium/ui/events/platform/x11/x11_events_platform.gyp43
-rw-r--r--chromium/ui/events/win/events_win.cc29
-rw-r--r--chromium/ui/events/x/device_data_manager.cc89
-rw-r--r--chromium/ui/events/x/device_data_manager.h29
-rw-r--r--chromium/ui/events/x/events_x.cc136
-rw-r--r--chromium/ui/events/x/events_x_unittest.cc237
-rw-r--r--chromium/ui/events/x/touch_factory_x11.cc58
-rw-r--r--chromium/ui/events/x/touch_factory_x11.h12
184 files changed, 18804 insertions, 2334 deletions
diff --git a/chromium/ui/events/BUILD.gn b/chromium/ui/events/BUILD.gn
new file mode 100644
index 00000000000..26c4b1ed633
--- /dev/null
+++ b/chromium/ui/events/BUILD.gn
@@ -0,0 +1,311 @@
+# 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 = [
+ "event_constants.h",
+ "event_switches.cc",
+ "event_switches.h",
+ "events_base_export.h",
+ "gesture_event_details.cc",
+ "gesture_event_details.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",
+ ]
+
+ defines = [ "EVENTS_BASE_IMPLEMENTATION" ]
+
+ deps = [
+ ":dom4_keycode_converter",
+ "//base",
+ "//base/third_party/dynamic_annotations",
+ "//skia",
+ "//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.cc",
+ "x/device_data_manager.h",
+ "x/device_list_cache_x.cc",
+ "x/device_list_cache_x.h",
+ "x/touch_factory_x11.cc",
+ "x/touch_factory_x11.h",
+ ]
+ }
+}
+
+component("events") {
+ deps = [
+ ":dom4_keycode_converter",
+ ":events_base",
+ ":gesture_detection",
+ "//skia",
+ "//ui/gfx",
+ "//ui/gfx/geometry",
+ ]
+
+ defines = [ "EVENTS_IMPLEMENTATION" ]
+
+ sources = [
+ "cocoa/cocoa_event_utils.h",
+ "cocoa/cocoa_event_utils.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_point.cc",
+ "gestures/gesture_point.h",
+ "gestures/gesture_recognizer.h",
+ "gestures/gesture_recognizer_impl.cc",
+ "gestures/gesture_recognizer_impl.h",
+ "gestures/gesture_recognizer_impl_mac.cc",
+ "gestures/gesture_sequence.cc",
+ "gestures/gesture_sequence.h",
+ "gestures/gesture_types.h",
+ "gestures/unified_gesture_detector_enabled.cc",
+ "gestures/unified_gesture_detector_enabled.h",
+ "gestures/velocity_calculator.cc",
+ "gestures/velocity_calculator.h",
+ "platform/x11/x11_event_source.cc",
+ "platform/x11/x11_event_source.h",
+ "win/events_win.cc",
+ "x/events_x.cc",
+ ]
+
+ if (use_x11) {
+ configs += [
+ "//build/config/linux:glib",
+ "//build/config/linux:x11",
+ ]
+ } else {
+ sources -= [
+ "platform/x11/x11_event_source.cc",
+ "platform/x11/x11_event_source.h",
+ "x/events_x.cc",
+ ]
+ }
+
+ 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/device/udev/device_manager_udev.cc",
+ "ozone/device/udev/device_manager_udev.h",
+ "ozone/evdev/event_converter_evdev.cc",
+ "ozone/evdev/event_converter_evdev.h",
+ "ozone/evdev/event_device_info.cc",
+ "ozone/evdev/event_device_info.h",
+ "ozone/evdev/event_factory_evdev.cc",
+ "ozone/evdev/event_factory_evdev.h",
+ "ozone/evdev/event_modifiers_evdev.cc",
+ "ozone/evdev/event_modifiers_evdev.h",
+ "ozone/evdev/key_event_converter_evdev.cc",
+ "ozone/evdev/key_event_converter_evdev.h",
+ "ozone/evdev/touch_event_converter_evdev.cc",
+ "ozone/evdev/touch_event_converter_evdev.h",
+ "ozone/event_factory_ozone.cc",
+ "ozone/event_factory_ozone.h",
+ "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",
+ ]
+ }
+
+ if (is_win || 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_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_config_helper.h",
+ "gesture_detection/gesture_provider.cc",
+ "gesture_detection/gesture_provider.h",
+ "gesture_detection/motion_event.h",
+ "gesture_detection/scale_gesture_detector.cc",
+ "gesture_detection/scale_gesture_detector.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("events_test_support") {
+ sources = [
+ "test/cocoa_test_event_utils.h",
+ "test/cocoa_test_event_utils.mm",
+ "test/events_test_utils.cc",
+ "test/events_test_utils.h",
+ "test/events_test_utils_x11.cc",
+ "test/events_test_utils_x11.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",
+ ]
+
+ deps = [
+ "//skia",
+ ":events_base",
+ ":events",
+ ]
+
+ if (is_ios) {
+ sources -= [
+ "test/cocoa_test_event_utils.h",
+ "test/cocoa_test_event_utils.mm",
+ ]
+ }
+
+ if (use_x11) {
+ configs += [ "//build/config/linux:x11" ]
+ } else {
+ sources -= [
+ "test/events_test_utils_x11.cc",
+ "test/events_test_utils_x11.h",
+ ]
+ }
+}
+
+test("events_unittests") {
+ sources = [
+ "cocoa/events_mac_unittest.mm",
+ "event_dispatcher_unittest.cc",
+ "event_processor_unittest.cc",
+ "event_rewriter_unittest.cc",
+ "event_unittest.cc",
+ "gestures/velocity_calculator_unittest.cc",
+ "gesture_detection/bitset_32_unittest.cc",
+ "gesture_detection/gesture_provider_unittest.cc",
+ "gesture_detection/mock_motion_event.h",
+ "gesture_detection/mock_motion_event.cc",
+ "gesture_detection/velocity_tracker_unittest.cc",
+ "gesture_detection/touch_disposition_gesture_filter_unittest.cc",
+ "keycodes/dom4/keycode_converter_unittest.cc",
+ "latency_info_unittest.cc",
+ "platform/platform_event_source_unittest.cc",
+ "x/events_x_unittest.cc",
+ ]
+
+ if (!use_x11) {
+ 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",
+ ]
+ }
+
+ deps = [
+ ":events",
+ ":events_base",
+ ":events_test_support",
+ ":gesture_detection",
+ "//base",
+ "//base/test:run_all_unittests",
+ "//skia",
+ "//testing/gtest",
+ "//ui/gfx:gfx_test_support",
+ ]
+}
diff --git a/chromium/ui/events/PRESUBMIT.py b/chromium/ui/events/PRESUBMIT.py
new file mode 100644
index 00000000000..dcdf2d5fbfd
--- /dev/null
+++ b/chromium/ui/events/PRESUBMIT.py
@@ -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.
+
+"""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_chromium_rel': tests,
+ 'linux_chromium_chromeos_rel': tests,
+ 'linux_chromeos_asan': tests,
+ 'win_chromium_compile_dbg': tests,
+ 'win_chromium_rel': tests,
+ }
+ }
diff --git a/chromium/ui/events/cocoa/cocoa_event_utils.h b/chromium/ui/events/cocoa/cocoa_event_utils.h
new file mode 100644
index 00000000000..a5984357537
--- /dev/null
+++ b/chromium/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/chromium/ui/events/cocoa/cocoa_event_utils.mm b/chromium/ui/events/cocoa/cocoa_event_utils.mm
new file mode 100644
index 00000000000..ade083c0eb2
--- /dev/null
+++ b/chromium/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/chromium/ui/events/cocoa/events_mac.mm b/chromium/ui/events/cocoa/events_mac.mm
index ffc34d7f2a7..7675eb6af05 100644
--- a/chromium/ui/events/cocoa/events_mac.mm
+++ b/chromium/ui/events/cocoa/events_mac.mm
@@ -1,59 +1,56 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// 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 <Cocoa/Cocoa.h>
+#include "ui/events/event_utils.h"
-#include "ui/events/event_constants.h"
+#include <Cocoa/Cocoa.h>
-#include "base/event_types.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 native_type = [native_event type];
- switch (native_type) {
+ 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 NSMouseMoved:
- return ET_MOUSE_MOVED;
-
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 NSKeyDown:
- return ET_KEY_PRESSED;
-
- case NSKeyUp:
- return ET_KEY_RELEASED;
-
+ case NSEventTypeSwipe:
+ return ET_SCROLL_FLING_START;
case NSFlagsChanged:
- return ET_KEY_PRESSED;
-
- case NSScrollWheel:
- return ET_MOUSEWHEEL;
-
case NSAppKitDefined:
case NSSystemDefined:
case NSApplicationDefined:
@@ -61,156 +58,176 @@ EventType EventTypeFromNative(const base::NativeEvent& native_event) {
case NSCursorUpdate:
case NSTabletPoint:
case NSTabletProximity:
+ case NSEventTypeGesture:
+ case NSEventTypeMagnify:
+ case NSEventTypeRotate:
+ case NSEventTypeBeginGesture:
+ case NSEventTypeEndGesture:
+ NOTIMPLEMENTED() << type;
+ break;
default:
- return ET_UNKNOWN;
+ NOTIMPLEMENTED() << type;
+ break;
}
+ return ET_UNKNOWN;
}
-int EventFlagsFromNative(const base::NativeEvent& native_event) {
- int event_flags = 0;
- NSUInteger modifiers = [native_event modifierFlags];
-
- if (modifiers & NSAlphaShiftKeyMask)
- event_flags = event_flags | EF_CAPS_LOCK_DOWN;
-
- if (modifiers & NSShiftKeyMask)
- event_flags = event_flags | EF_SHIFT_DOWN;
-
- if (modifiers & NSControlKeyMask)
- event_flags = event_flags | EF_CONTROL_DOWN;
-
- if (modifiers & NSAlternateKeyMask)
- event_flags = event_flags | EF_ALT_DOWN;
-
- if (modifiers & NSCommandKeyMask)
- event_flags = event_flags | EF_COMMAND_DOWN;
-
- NSEventType type = [native_event type];
-
- if (type == NSLeftMouseDown ||
- type == NSLeftMouseUp ||
- type == NSLeftMouseDragged) {
- event_flags = event_flags | EF_LEFT_MOUSE_BUTTON;
- }
-
- if (type == NSRightMouseDown ||
- type == NSRightMouseUp ||
- type == NSRightMouseDragged) {
- event_flags = event_flags | EF_RIGHT_MOUSE_BUTTON;
- }
-
- if (type == NSOtherMouseDown ||
- type == NSOtherMouseUp ||
- type == NSOtherMouseDragged) {
- event_flags = event_flags | EF_MIDDLE_MOUSE_BUTTON;
- }
-
- return event_flags;
+int EventFlagsFromNative(const base::NativeEvent& event) {
+ NSUInteger modifiers = [event modifierFlags];
+ return EventFlagsFromNSEventWithModifiers(event, modifiers);
}
base::TimeDelta EventTimeFromNative(const base::NativeEvent& native_event) {
- return base::TimeDelta::FromMicroseconds(
- [native_event timestamp] * 1000000.0f);
+ 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];
-
- // Convert |location| to be relative to coordinate system of |contentView|.
- // Note: this assumes that ui::Event coordinates are rooted in the top-level
- // view (with flipped coordinates). A more general (but costly) approach
- // would be to hit-test the view of the event and use the found view's
- // coordinate system. Currently there is no need for this generality, and
- // speed is preferred. Flipped views are not suppported.
- DCHECK([[window contentView] isFlipped] == NO);
- location = [[window contentView] convertPoint:location fromView:nil];
- location.y = [[window contentView] bounds].size.height - location.y;
-
- return gfx::Point(NSPointToCGPoint(location));
+ 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) {
- // TODO(port): Needs to always return screen position here. Returning normal
- // origin for now since that's obviously wrong.
- return gfx::Point(0, 0);
+ NOTIMPLEMENTED();
+ return gfx::Point();
}
-KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
- return ui::KeyboardCodeFromNSEvent(native_event);
+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;
}
-std::string CodeFromNative(const base::NativeEvent& native_event) {
- return ui::CodeFromNSEvent(native_event);
+gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& event) {
+ // 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]);
}
-bool IsMouseEvent(const base::NativeEvent& native_event) {
- EventType type = EventTypeFromNative(native_event);
- 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;
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+ return [event copy];
}
-gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
- // TODO(dhollowa): Come back to this once comparisons can be made with other
- // platforms.
- return gfx::Vector2d([native_event deltaX], [native_event deltaY]);
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+ [event release];
}
-void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
- // Touch is currently unsupported.
+void ClearTouchIdIfReleased(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
}
int GetTouchId(const base::NativeEvent& native_event) {
- // Touch is currently unsupported.
+ NOTIMPLEMENTED();
return 0;
}
float GetTouchRadiusX(const base::NativeEvent& native_event) {
- // Touch is currently unsupported.
- return 1.0;
+ NOTIMPLEMENTED();
+ return 0.f;
}
float GetTouchRadiusY(const base::NativeEvent& native_event) {
- // Touch is currently unsupported.
- return 1.0;
+ NOTIMPLEMENTED();
+ return 0.f;
}
float GetTouchAngle(const base::NativeEvent& native_event) {
- // Touch is currently unsupported.
- return 0.0;
+ NOTIMPLEMENTED();
+ return 0.f;
}
float GetTouchForce(const base::NativeEvent& native_event) {
- // Touch is currently unsupported.
- return 0.0;
+ 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 IsNoopEvent(const base::NativeEvent& event) {
- return ([event type] == NSApplicationDefined && [event subtype] == 0);
+bool GetFlingData(const base::NativeEvent& native_event,
+ float* vx,
+ float* vy,
+ float* vx_ordinal,
+ float* vy_ordinal,
+ bool* is_cancel) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool GetGestureTimes(const base::NativeEvent& native_event,
+ double* start_time,
+ double* end_time) {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void SetNaturalScroll(bool enabled) {
+ NOTIMPLEMENTED();
+}
+
+bool IsNaturalScrollEnabled() {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+bool IsTouchpadEvent(const base::NativeEvent& native_event) {
+ 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);
}
-base::NativeEvent CreateNoopEvent() {
- return [NSEvent otherEventWithType:NSApplicationDefined
- location:NSZeroPoint
- modifierFlags:0
- timestamp:[NSDate timeIntervalSinceReferenceDate]
- windowNumber:0
- context:nil
- subtype:0
- data1:0
- data2:0];
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ return native_event.keyCode;
}
} // namespace ui
diff --git a/chromium/ui/events/cocoa/events_mac_unittest.mm b/chromium/ui/events/cocoa/events_mac_unittest.mm
new file mode 100644
index 00000000000..77f85495f48
--- /dev/null
+++ b/chromium/ui/events/cocoa/events_mac_unittest.mm
@@ -0,0 +1,315 @@
+// 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"
+#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
+- (NSUInteger)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 ScopedClassSwizzler(
+ [NSEvent class],
+ [MiddleMouseButtonNumberDonor class],
+ @selector(buttonNumber)));
+ }
+
+ void SwizzleTestWindow() {
+ DCHECK(!g_test_window);
+ DCHECK(!swizzler_);
+ g_test_window = test_window();
+ swizzler_.reset(new ScopedClassSwizzler(
+ [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<ScopedClassSwizzler> 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/chromium/ui/events/event.cc b/chromium/ui/events/event.cc
index 246e39721f2..ed7ff3adaea 100644
--- a/chromium/ui/events/event.cc
+++ b/chromium/ui/events/event.cc
@@ -5,6 +5,7 @@
#include "ui/events/event.h"
#if defined(USE_X11)
+#include <X11/extensions/XInput2.h>
#include <X11/Xlib.h>
#endif
@@ -28,24 +29,6 @@
namespace {
-base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
-#if defined(USE_X11)
- if (!event || event->type == GenericEvent)
- return NULL;
- XEvent* copy = new XEvent;
- *copy = *event;
- return copy;
-#elif defined(OS_WIN)
- return event;
-#elif defined(USE_OZONE)
- return NULL;
-#else
- NOTREACHED() <<
- "Don't know how to copy base::NativeEvent for this platform";
- return NULL;
-#endif
-}
-
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
@@ -64,7 +47,6 @@ std::string EventTypeName(ui::EventType type) {
CASE_TYPE(ET_TOUCH_RELEASED);
CASE_TYPE(ET_TOUCH_PRESSED);
CASE_TYPE(ET_TOUCH_MOVED);
- CASE_TYPE(ET_TOUCH_STATIONARY);
CASE_TYPE(ET_TOUCH_CANCELLED);
CASE_TYPE(ET_DROP_TARGET_EVENT);
CASE_TYPE(ET_TRANSLATED_KEY_PRESS);
@@ -73,6 +55,7 @@ std::string EventTypeName(ui::EventType type) {
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);
@@ -84,7 +67,9 @@ std::string EventTypeName(ui::EventType type) {
CASE_TYPE(ET_GESTURE_PINCH_UPDATE);
CASE_TYPE(ET_GESTURE_LONG_PRESS);
CASE_TYPE(ET_GESTURE_LONG_TAP);
- CASE_TYPE(ET_GESTURE_MULTIFINGER_SWIPE);
+ 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);
@@ -101,10 +86,22 @@ std::string EventTypeName(ui::EventType type) {
bool IsX11SendEventTrue(const base::NativeEvent& event) {
#if defined(USE_X11)
- if (event && event->xany.send_event)
- return true;
+ 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
@@ -115,10 +112,8 @@ namespace ui {
// Event
Event::~Event() {
-#if defined(USE_X11)
if (delete_native_event_)
- delete native_event_;
-#endif
+ ReleaseCopiedNativeEvent(native_event_);
}
bool Event::HasNativeEvent() const {
@@ -147,9 +142,7 @@ Event::Event(EventType type, base::TimeDelta time_stamp, int flags)
: type_(type),
time_stamp_(time_stamp),
flags_(flags),
-#if defined(USE_X11)
- native_event_(NULL),
-#endif
+ native_event_(base::NativeEvent()),
delete_native_event_(false),
cancelable_(true),
target_(NULL),
@@ -157,7 +150,6 @@ Event::Event(EventType type, base::TimeDelta time_stamp, int flags)
result_(ER_UNHANDLED) {
if (type_ < ET_LAST)
name_ = EventTypeName(type_);
- Init();
}
Event::Event(const base::NativeEvent& native_event,
@@ -166,6 +158,7 @@ Event::Event(const base::NativeEvent& native_event,
: type_(type),
time_stamp_(EventTimeFromNative(native_event)),
flags_(flags),
+ native_event_(native_event),
delete_native_event_(false),
cancelable_(true),
target_(NULL),
@@ -175,18 +168,17 @@ Event::Event(const base::NativeEvent& native_event,
if (type_ < ET_LAST)
name_ = EventTypeName(type_);
UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser",
- delta.InMicroseconds(), 0, 1000000, 100);
+ 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,
- 0,
+ 1,
1000000,
100,
base::HistogramBase::kUmaTargetedHistogramFlag);
counter_for_type->Add(delta.InMicroseconds());
- InitWithNativeEvent(native_event);
}
Event::Event(const Event& copy)
@@ -194,18 +186,14 @@ Event::Event(const Event& copy)
time_stamp_(copy.time_stamp_),
latency_(copy.latency_),
flags_(copy.flags_),
- native_event_(::CopyNativeEvent(copy.native_event_)),
- delete_native_event_(false),
+ native_event_(CopyNativeEvent(copy.native_event_)),
+ delete_native_event_(true),
cancelable_(true),
target_(NULL),
phase_(EP_PREDISPATCH),
result_(ER_UNHANDLED) {
if (type_ < ET_LAST)
name_ = EventTypeName(type_);
-#if defined(USE_X11)
- if (native_event_)
- delete_native_event_ = true;
-#endif
}
void Event::SetType(EventType type) {
@@ -216,14 +204,6 @@ void Event::SetType(EventType type) {
name_ = EventTypeName(type_);
}
-void Event::Init() {
- std::memset(&native_event_, 0, sizeof(native_event_));
-}
-
-void Event::InitWithNativeEvent(const base::NativeEvent& native_event) {
- native_event_ = native_event;
-}
-
////////////////////////////////////////////////////////////////////////////////
// CancelModeEvent
@@ -250,8 +230,8 @@ LocatedEvent::LocatedEvent(const base::NativeEvent& native_event)
}
LocatedEvent::LocatedEvent(EventType type,
- const gfx::Point& location,
- const gfx::Point& root_location,
+ const gfx::PointF& location,
+ const gfx::PointF& root_location,
base::TimeDelta time_stamp,
int flags)
: Event(type, time_stamp, flags),
@@ -264,7 +244,8 @@ void LocatedEvent::UpdateForRootTransform(
// Transform has to be done at root level.
gfx::Point3F p(location_);
reversed_root_transform.TransformPoint(&p);
- root_location_ = location_ = gfx::ToFlooredPoint(p.AsPointF());
+ location_ = p.AsPointF();
+ root_location_ = location_;
}
////////////////////////////////////////////////////////////////////////////////
@@ -279,11 +260,12 @@ MouseEvent::MouseEvent(const base::NativeEvent& native_event)
}
MouseEvent::MouseEvent(EventType type,
- const gfx::Point& location,
- const gfx::Point& root_location,
- int flags)
+ 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_(0) {
+ changed_button_flags_(changed_button_flags) {
if (this->type() == ET_MOUSE_MOVED && IsAnyButton())
SetType(ET_MOUSE_DRAGGED);
}
@@ -311,10 +293,10 @@ bool MouseEvent::IsRepeatedClickEvent(
if (time_difference.InMilliseconds() > kDoubleClickTimeMS)
return false;
- if (abs(event2.x() - event1.x()) > kDoubleClickWidth / 2)
+ if (std::abs(event2.x() - event1.x()) > kDoubleClickWidth / 2)
return false;
- if (abs(event2.y() - event1.y()) > kDoubleClickHeight / 2)
+ if (std::abs(event2.y() - event1.y()) > kDoubleClickHeight / 2)
return false;
return true;
@@ -324,23 +306,46 @@ bool MouseEvent::IsRepeatedClickEvent(
int MouseEvent::GetRepeatCount(const MouseEvent& event) {
int click_count = 1;
if (last_click_event_) {
- if (event.type() == ui::ET_MOUSE_RELEASED)
- return last_click_event_->GetClickCount();
- if (IsX11SendEventTrue(event.native_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))
+ } 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)
@@ -436,19 +441,25 @@ TouchEvent::TouchEvent(const base::NativeEvent& native_event)
radius_x_(GetTouchRadiusX(native_event)),
radius_y_(GetTouchRadiusY(native_event)),
rotation_angle_(GetTouchAngle(native_event)),
- force_(GetTouchForce(native_event)) {
+ force_(GetTouchForce(native_event)),
+ source_device_id_(-1) {
latency()->AddLatencyNumberWithTimestamp(
INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
0,
0,
base::TimeTicks::FromInternalValue(time_stamp().ToInternalValue()),
- 1,
- true);
+ 1);
+
+#if defined(USE_X11)
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(native_event->xcookie.data);
+ source_device_id_ = xiev->deviceid;
+#endif
+
latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
}
TouchEvent::TouchEvent(EventType type,
- const gfx::Point& location,
+ const gfx::PointF& location,
int touch_id,
base::TimeDelta time_stamp)
: LocatedEvent(type, location, location, time_stamp, 0),
@@ -456,12 +467,13 @@ TouchEvent::TouchEvent(EventType type,
radius_x_(0.0f),
radius_y_(0.0f),
rotation_angle_(0.0f),
- force_(0.0f) {
+ force_(0.0f),
+ source_device_id_(-1) {
latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
}
TouchEvent::TouchEvent(EventType type,
- const gfx::Point& location,
+ const gfx::PointF& location,
int flags,
int touch_id,
base::TimeDelta time_stamp,
@@ -474,7 +486,8 @@ TouchEvent::TouchEvent(EventType type,
radius_x_(radius_x),
radius_y_(radius_y),
rotation_angle_(angle),
- force_(force) {
+ force_(force),
+ source_device_id_(-1) {
latency()->AddLatencyNumber(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, 0);
}
@@ -486,11 +499,6 @@ TouchEvent::~TouchEvent() {
ClearTouchIdIfReleased(native_event());
}
-void TouchEvent::Relocate(const gfx::Point& origin) {
- location_ -= origin.OffsetFromOrigin();
- root_location_ -= origin.OffsetFromOrigin();
-}
-
void TouchEvent::UpdateForRootTransform(
const gfx::Transform& inverted_root_transform) {
LocatedEvent::UpdateForRootTransform(inverted_root_transform);
@@ -506,6 +514,42 @@ void TouchEvent::UpdateForRootTransform(
////////////////////////////////////////////////////////////////////////////////
// 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, bool is_char)
: Event(native_event,
EventTypeFromNative(native_event),
@@ -513,7 +557,11 @@ KeyEvent::KeyEvent(const base::NativeEvent& native_event, bool is_char)
key_code_(KeyboardCodeFromNative(native_event)),
code_(CodeFromNative(native_event)),
is_char_(is_char),
+ platform_keycode_(PlatformKeycodeFromNative(native_event)),
character_(0) {
+ if (IsRepeated(*this))
+ set_flags(flags() | ui::EF_IS_REPEAT);
+
#if defined(USE_X11)
NormalizeFlags();
#endif
@@ -526,6 +574,7 @@ KeyEvent::KeyEvent(EventType type,
: Event(type, EventTimeForNow(), flags),
key_code_(key_code),
is_char_(is_char),
+ platform_keycode_(0),
character_(GetCharacterFromKeyCode(key_code, flags)) {
}
@@ -538,6 +587,7 @@ KeyEvent::KeyEvent(EventType type,
key_code_(key_code),
code_(code),
is_char_(is_char),
+ platform_keycode_(0),
character_(GetCharacterFromKeyCode(key_code, flags)) {
}
@@ -555,10 +605,13 @@ uint16 KeyEvent::GetCharacter() const {
DCHECK(native_event()->type == KeyPress ||
native_event()->type == KeyRelease);
- uint16 ch = 0;
- if (!IsControlDown())
- ch = GetCharacterFromXEvent(native_event());
- return ch ? ch : GetCharacterFromKeyCode(key_code_, flags());
+ // 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 ||
@@ -570,6 +623,7 @@ uint16 KeyEvent::GetCharacter() const {
}
bool KeyEvent::IsUnicodeKeyCode() const {
+#if defined(OS_WIN)
if (!IsAltDown())
return false;
const int key = key_code();
@@ -583,6 +637,9 @@ bool KeyEvent::IsUnicodeKeyCode() const {
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() {
@@ -609,28 +666,33 @@ void KeyEvent::NormalizeFlags() {
set_flags(flags() & ~mask);
}
-////////////////////////////////////////////////////////////////////////////////
-// TranslatedKeyEvent
-
-TranslatedKeyEvent::TranslatedKeyEvent(const base::NativeEvent& native_event,
- bool is_char)
- : KeyEvent(native_event, is_char) {
- SetType(type() == ET_KEY_PRESSED ?
- ET_TRANSLATED_KEY_PRESS : ET_TRANSLATED_KEY_RELEASE);
-}
-
-TranslatedKeyEvent::TranslatedKeyEvent(bool is_press,
- KeyboardCode key_code,
- int flags)
- : KeyEvent((is_press ? ET_TRANSLATED_KEY_PRESS : ET_TRANSLATED_KEY_RELEASE),
- key_code,
- flags,
- false) {
+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 TranslatedKeyEvent::ConvertToKeyEvent() {
- SetType(type() == ET_TRANSLATED_KEY_PRESS ?
- ET_KEY_PRESSED : ET_KEY_RELEASED);
+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();
+ }
}
////////////////////////////////////////////////////////////////////////////////
@@ -656,7 +718,7 @@ ScrollEvent::ScrollEvent(const base::NativeEvent& native_event)
}
ScrollEvent::ScrollEvent(EventType type,
- const gfx::Point& location,
+ const gfx::PointF& location,
base::TimeDelta time_stamp,
int flags,
float x_offset,
@@ -664,7 +726,7 @@ ScrollEvent::ScrollEvent(EventType type,
float x_offset_ordinal,
float y_offset_ordinal,
int finger_count)
- : MouseEvent(type, location, location, flags),
+ : MouseEvent(type, location, location, flags, 0),
x_offset_(x_offset),
y_offset_(y_offset),
x_offset_ordinal_(x_offset_ordinal),
@@ -685,15 +747,15 @@ void ScrollEvent::Scale(const float factor) {
// GestureEvent
GestureEvent::GestureEvent(EventType type,
- int x,
- int y,
+ float x,
+ float y,
int flags,
base::TimeDelta time_stamp,
const GestureEventDetails& details,
unsigned int touch_ids_bitfield)
: LocatedEvent(type,
- gfx::Point(x, y),
- gfx::Point(x, y),
+ gfx::PointF(x, y),
+ gfx::PointF(x, y),
time_stamp,
flags | EF_FROM_TOUCH),
details_(details),
diff --git a/chromium/ui/events/event.h b/chromium/ui/events/event.h
index b8a341a2234..9c0c580707b 100644
--- a/chromium/ui/events/event.h
+++ b/chromium/ui/events/event.h
@@ -8,13 +8,16 @@
#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/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;
@@ -79,6 +82,7 @@ class EVENTS_EXPORT Event {
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 IsRepeat() const { return (flags_ & EF_IS_REPEAT) != 0; }
bool IsKeyEvent() const {
return type_ == ET_KEY_PRESSED ||
@@ -102,7 +106,6 @@ class EVENTS_EXPORT Event {
return type_ == ET_TOUCH_RELEASED ||
type_ == ET_TOUCH_PRESSED ||
type_ == ET_TOUCH_MOVED ||
- type_ == ET_TOUCH_STATIONARY ||
type_ == ET_TOUCH_CANCELLED;
}
@@ -122,8 +125,9 @@ class EVENTS_EXPORT Event {
case ET_GESTURE_PINCH_UPDATE:
case ET_GESTURE_LONG_PRESS:
case ET_GESTURE_LONG_TAP:
- case ET_GESTURE_MULTIFINGER_SWIPE:
+ 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;
@@ -216,10 +220,6 @@ class EVENTS_EXPORT Event {
private:
friend class EventTestApi;
- // Safely initializes the native event members of this class.
- void Init();
- void InitWithNativeEvent(const base::NativeEvent& native_event);
-
EventType type_;
std::string name_;
base::TimeDelta time_stamp_;
@@ -243,14 +243,22 @@ class EVENTS_EXPORT LocatedEvent : public Event {
public:
virtual ~LocatedEvent();
- int x() const { return location_.x(); }
- int y() const { return location_.y(); }
- void set_location(const gfx::Point& location) { location_ = location; }
- gfx::Point location() const { return location_; }
- void set_root_location(const gfx::Point& root_location) {
+ 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 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_|.
@@ -258,8 +266,14 @@ class EVENTS_EXPORT LocatedEvent : public Event {
const gfx::Transform& inverted_root_transform);
template <class T> void ConvertLocationToTarget(T* source, T* target) {
- if (target && target != source)
- T::ConvertPointToTarget(source, target, &location_);
+ 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:
@@ -279,16 +293,16 @@ class EVENTS_EXPORT LocatedEvent : public Event {
// Used for synthetic events in testing.
LocatedEvent(EventType type,
- const gfx::Point& location,
- const gfx::Point& root_location,
+ const gfx::PointF& location,
+ const gfx::PointF& root_location,
base::TimeDelta time_stamp,
int flags);
- gfx::Point location_;
+ gfx::PointF location_;
// |location_| multiplied by an optional transformation matrix for
// rotations, animations and skews.
- gfx::Point root_location_;
+ gfx::PointF root_location_;
};
class EVENTS_EXPORT MouseEvent : public LocatedEvent {
@@ -319,9 +333,10 @@ class EVENTS_EXPORT MouseEvent : public LocatedEvent {
// Used for synthetic events in testing and by the gesture recognizer.
MouseEvent(EventType type,
- const gfx::Point& location,
- const gfx::Point& root_location,
- int flags);
+ 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 {
@@ -376,14 +391,25 @@ class EVENTS_EXPORT MouseEvent : public LocatedEvent {
int changed_button_flags() const { return changed_button_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;
@@ -401,11 +427,9 @@ class EVENTS_EXPORT MouseWheelEvent : public MouseEvent {
template <class T>
MouseWheelEvent(const MouseWheelEvent& model,
T* source,
- T* target,
- EventType type,
- int flags)
- : MouseEvent(model, source, target, type, flags),
- offset_(model.x_offset(), model.y_offset()){
+ T* target)
+ : MouseEvent(model, source, target, model.type(), model.flags()),
+ offset_(model.x_offset(), model.y_offset()) {
}
// The amount to scroll. This is in multiples of kWheelDelta.
@@ -436,16 +460,17 @@ class EVENTS_EXPORT TouchEvent : public LocatedEvent {
radius_x_(model.radius_x_),
radius_y_(model.radius_y_),
rotation_angle_(model.rotation_angle_),
- force_(model.force_) {
+ force_(model.force_),
+ source_device_id_(model.source_device_id_) {
}
TouchEvent(EventType type,
- const gfx::Point& root_location,
+ const gfx::PointF& location,
int touch_id,
base::TimeDelta time_stamp);
TouchEvent(EventType type,
- const gfx::Point& location,
+ const gfx::PointF& location,
int flags,
int touch_id,
base::TimeDelta timestamp,
@@ -461,15 +486,14 @@ class EVENTS_EXPORT TouchEvent : public LocatedEvent {
float radius_y() const { return radius_y_; }
float rotation_angle() const { return rotation_angle_; }
float force() const { return force_; }
-
- // Relocate the touch-point to a new |origin|.
- // This is useful when touch event is in X Root Window coordinates,
- // and it needs to be mapped into Aura Root Window coordinates.
- void Relocate(const gfx::Point& origin);
+ int source_device_id() const { return source_device_id_; }
// Used for unit tests.
void set_radius_x(const float r) { radius_x_ = r; }
void set_radius_y(const float r) { radius_y_ = r; }
+ void set_source_device_id(int source_device_id) {
+ source_device_id_ = source_device_id;
+ }
// Overridden from LocatedEvent.
virtual void UpdateForRootTransform(
@@ -503,6 +527,9 @@ class EVENTS_EXPORT TouchEvent : public LocatedEvent {
// Force (pressure) of the touch. Normalized to be [0, 1]. Default to be 0.0.
float force_;
+
+ // The device id of the screen the event came from. Default to be -1.
+ int source_device_id_;
};
class EVENTS_EXPORT KeyEvent : public Event {
@@ -526,6 +553,8 @@ class EVENTS_EXPORT KeyEvent : public Event {
// BMP characters.
uint16 GetCharacter() const;
+ // Gets the platform key code. For XKB, this is the xksym value.
+ uint32 platform_keycode() const { return platform_keycode_; }
KeyboardCode key_code() const { return key_code_; }
bool is_char() const { return is_char_; }
@@ -545,6 +574,16 @@ class EVENTS_EXPORT KeyEvent : public Event {
// in http://crbug.com/127142#c8, the normalization is necessary.
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:
+ // This allows a subclass TranslatedKeyEvent to be a non character event.
+ void set_is_char(bool is_char) { is_char_ = is_char; }
+
private:
KeyboardCode key_code_;
@@ -559,28 +598,21 @@ class EVENTS_EXPORT KeyEvent : public Event {
// share the same type: ET_KEY_PRESSED.
bool is_char_;
- uint16 character_;
-};
-
-// A key event which is translated by an input method (IME).
-// For example, if an IME receives a KeyEvent(VKEY_SPACE), and it does not
-// consume the key, the IME usually generates and dispatches a
-// TranslatedKeyEvent(VKEY_SPACE) event. If the IME receives a KeyEvent and
-// it does consume the event, it might dispatch a
-// TranslatedKeyEvent(VKEY_PROCESSKEY) event as defined in the DOM spec.
-class EVENTS_EXPORT TranslatedKeyEvent : public KeyEvent {
- public:
- TranslatedKeyEvent(const base::NativeEvent& native_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_;
- // Used for synthetic events such as a VKEY_PROCESSKEY key event.
- TranslatedKeyEvent(bool is_press, KeyboardCode key_code, int flags);
+ // 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'.
+ uint16 character_;
- // Changes the type() of the object from ET_TRANSLATED_KEY_* to ET_KEY_* so
- // that RenderWidgetHostViewAura and NativeWidgetAura could handle the event.
- void ConvertToKeyEvent();
+ static bool IsRepeated(const KeyEvent& event);
- private:
- DISALLOW_COPY_AND_ASSIGN(TranslatedKeyEvent);
+ static KeyEvent* last_key_event_;
};
class EVENTS_EXPORT ScrollEvent : public MouseEvent {
@@ -600,7 +632,7 @@ class EVENTS_EXPORT ScrollEvent : public MouseEvent {
// Used for tests.
ScrollEvent(EventType type,
- const gfx::Point& location,
+ const gfx::PointF& location,
base::TimeDelta time_stamp,
int flags,
float x_offset,
@@ -634,8 +666,8 @@ class EVENTS_EXPORT ScrollEvent : public MouseEvent {
class EVENTS_EXPORT GestureEvent : public LocatedEvent {
public:
GestureEvent(EventType type,
- int x,
- int y,
+ float x,
+ float y,
int flags,
base::TimeDelta time_stamp,
const GestureEventDetails& details,
diff --git a/chromium/ui/events/event_constants.h b/chromium/ui/events/event_constants.h
index 2a545b740ae..d2ff97c7aec 100644
--- a/chromium/ui/events/event_constants.h
+++ b/chromium/ui/events/event_constants.h
@@ -23,7 +23,6 @@ enum EventType {
ET_TOUCH_RELEASED,
ET_TOUCH_PRESSED,
ET_TOUCH_MOVED,
- ET_TOUCH_STATIONARY,
ET_TOUCH_CANCELLED,
ET_DROP_TARGET_EVENT,
ET_TRANSLATED_KEY_PRESS,
@@ -31,29 +30,37 @@ enum EventType {
// 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_BEGIN, // Sent before any other gesture types.
- ET_GESTURE_END, // Sent after any other gestures.
+ 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 TAP_UP gesture if the
- // finger(s) were moving quickly before they are released.
- ET_GESTURE_MULTIFINGER_SWIPE,
+ // 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.
@@ -78,19 +85,34 @@ enum EventFlags {
EF_LEFT_MOUSE_BUTTON = 1 << 4,
EF_MIDDLE_MOUSE_BUTTON = 1 << 5,
EF_RIGHT_MOUSE_BUTTON = 1 << 6,
- EF_COMMAND_DOWN = 1 << 7, // Only useful on OSX
+ 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
};
// 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_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.
diff --git a/chromium/ui/events/event_dispatcher.cc b/chromium/ui/events/event_dispatcher.cc
index a1fbafa79c0..ca24cde3ff0 100644
--- a/chromium/ui/events/event_dispatcher.cc
+++ b/chromium/ui/events/event_dispatcher.cc
@@ -51,11 +51,18 @@ EventDispatchDetails EventDispatcherDelegate::DispatchEvent(EventTarget* target,
dispatch_helper.set_result(ER_UNHANDLED);
EventDispatchDetails details = PreDispatchEvent(target, event);
- if (!event->handled() && !details.dispatcher_destroyed)
+ if (!event->handled() &&
+ !details.dispatcher_destroyed &&
+ !details.target_destroyed) {
details = DispatchEventToTarget(target, event);
- if (!details.dispatcher_destroyed)
- details = PostDispatchEvent(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;
}
@@ -81,7 +88,11 @@ EventDispatchDetails EventDispatcherDelegate::DispatchEventToTarget(
else if (old_dispatcher)
old_dispatcher->OnDispatcherDelegateDestroyed();
- return dispatcher.details();
+ EventDispatchDetails details;
+ details.dispatcher_destroyed = dispatcher.delegate_destroyed();
+ details.target_destroyed =
+ (!details.dispatcher_destroyed && !CanDispatchToTarget(target));
+ return details;
}
////////////////////////////////////////////////////////////////////////////////
@@ -128,14 +139,9 @@ void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) {
return;
}
- if (!delegate_)
+ if (!delegate_ || !delegate_->CanDispatchToTarget(target))
return;
- if (!delegate_->CanDispatchToTarget(target)) {
- details_.target_destroyed = true;
- return;
- }
-
handler_list_.clear();
target->GetPostTargetHandlers(&handler_list_);
dispatch_helper.set_phase(EP_POSTTARGET);
@@ -143,7 +149,6 @@ void EventDispatcher::ProcessEvent(EventTarget* target, Event* event) {
}
void EventDispatcher::OnDispatcherDelegateDestroyed() {
- details_.dispatcher_destroyed = true;
delegate_ = NULL;
}
@@ -175,7 +180,6 @@ void EventDispatcher::DispatchEventToEventHandlers(EventHandlerList* list,
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())) {
- details_.target_destroyed = true;
if (event->cancelable())
event->StopPropagation();
return;
diff --git a/chromium/ui/events/event_dispatcher.h b/chromium/ui/events/event_dispatcher.h
index 9f3a450e76e..a8c985e6813 100644
--- a/chromium/ui/events/event_dispatcher.h
+++ b/chromium/ui/events/event_dispatcher.h
@@ -54,6 +54,7 @@ class EVENTS_EXPORT EventDispatcherDelegate {
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;
@@ -80,7 +81,6 @@ class EVENTS_EXPORT EventDispatcher {
Event* current_event() { return current_event_; }
bool delegate_destroyed() const { return !delegate_; }
- const EventDispatchDetails& details() const { return details_; }
void OnHandlerDestroyed(EventHandler* handler);
void OnDispatcherDelegateDestroyed();
@@ -99,8 +99,6 @@ class EVENTS_EXPORT EventDispatcher {
EventHandlerList handler_list_;
- EventDispatchDetails details_;
-
DISALLOW_COPY_AND_ASSIGN(EventDispatcher);
};
diff --git a/chromium/ui/events/event_dispatcher_unittest.cc b/chromium/ui/events/event_dispatcher_unittest.cc
index 30bbd0dd997..248784a8870 100644
--- a/chromium/ui/events/event_dispatcher_unittest.cc
+++ b/chromium/ui/events/event_dispatcher_unittest.cc
@@ -209,10 +209,8 @@ class TestEventDispatcher : public EventDispatcherDelegate {
virtual ~TestEventDispatcher() {}
- void ProcessEvent(EventTarget* target, Event* event) {
- EventDispatchDetails details = DispatchEvent(target, event);
- if (details.dispatcher_destroyed)
- return;
+ EventDispatchDetails ProcessEvent(EventTarget* target, Event* event) {
+ return DispatchEvent(target, event);
}
private:
@@ -258,7 +256,7 @@ TEST(EventDispatcherTest, EventDispatchOrder) {
h8.set_expect_post_target(true);
MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
- gfx::Point(3, 4), 0);
+ gfx::Point(3, 4), 0, 0);
Event::DispatcherApi event_mod(&mouse);
dispatcher.ProcessEvent(&child, &mouse);
EXPECT_FALSE(mouse.stopped_propagation());
@@ -332,7 +330,7 @@ TEST(EventDispatcherTest, EventDispatchPhase) {
handler.set_expect_post_target(true);
MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
- gfx::Point(3, 4), 0);
+ gfx::Point(3, 4), 0, 0);
Event::DispatcherApi event_mod(&mouse);
dispatcher.ProcessEvent(&target, &mouse);
EXPECT_EQ(ER_UNHANDLED, mouse.result());
@@ -364,9 +362,9 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
h2.set_expect_pre_target(false);
MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
- gfx::Point(3, 4), 0);
- Event::DispatcherApi event_mod(&mouse);
- dispatcher->ProcessEvent(&target, &mouse);
+ 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]);
@@ -391,8 +389,8 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
h2.set_expect_pre_target(false);
NonCancelableEvent event;
- Event::DispatcherApi event_mod(&event);
- dispatcher->ProcessEvent(&target, &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]);
@@ -416,9 +414,9 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
h2.set_expect_post_target(false);
MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
- gfx::Point(3, 4), 0);
- Event::DispatcherApi event_mod(&mouse);
- dispatcher->ProcessEvent(&target, &mouse);
+ 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]);
@@ -443,8 +441,8 @@ TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
h2.set_expect_post_target(false);
NonCancelableEvent event;
- Event::DispatcherApi event_mod(&event);
- dispatcher->ProcessEvent(&target, &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]);
@@ -469,8 +467,11 @@ TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) {
// |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);
- dispatcher.ProcessEvent(&target, &mouse);
+ 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());
@@ -480,9 +481,10 @@ TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) {
// Test for non-cancelable event.
target.Reset();
NonCancelableEvent event;
- dispatcher.ProcessEvent(&target, &event);
+ details = dispatcher.ProcessEvent(&target, &event);
+ 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]);
@@ -508,8 +510,11 @@ TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) {
// destroyed it.
h3->set_expect_pre_target(false);
- MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0);
- dispatcher.ProcessEvent(&target, &mouse);
+ 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]);
@@ -533,7 +538,9 @@ TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) {
h3->set_expect_pre_target(false);
NonCancelableEvent event;
- dispatcher.ProcessEvent(&target, &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]);
@@ -561,8 +568,10 @@ TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) {
// it.
h3->set_expect_pre_target(false);
- MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0);
- dispatcher->ProcessEvent(&target, &mouse);
+ 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]);
@@ -589,7 +598,8 @@ TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) {
h3->set_expect_pre_target(false);
NonCancelableEvent event;
- dispatcher->ProcessEvent(&target, &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]);
diff --git a/chromium/ui/events/event_processor.cc b/chromium/ui/events/event_processor.cc
index 1a50d2b7411..3077cc85b16 100644
--- a/chromium/ui/events/event_processor.cc
+++ b/chromium/ui/events/event_processor.cc
@@ -14,12 +14,22 @@ EventDispatchDetails EventProcessor::OnEventFromSource(Event* event) {
CHECK(root);
EventTargeter* targeter = root->GetEventTargeter();
CHECK(targeter);
+
PrepareEventForDispatch(event);
EventTarget* target = targeter->FindTargetForEvent(root, event);
- if (!target)
- return EventDispatchDetails();
- return DispatchEvent(target, event);
+ while (target) {
+ EventDispatchDetails details = DispatchEvent(target, event);
+ if (details.dispatcher_destroyed ||
+ details.target_destroyed ||
+ event->handled()) {
+ return details;
+ }
+
+ target = targeter->FindNextBestTarget(target, event);
+ }
+
+ return EventDispatchDetails();
}
void EventProcessor::PrepareEventForDispatch(Event* event) {
diff --git a/chromium/ui/events/event_processor.h b/chromium/ui/events/event_processor.h
index 34c973054d5..4ce1ef0522e 100644
--- a/chromium/ui/events/event_processor.h
+++ b/chromium/ui/events/event_processor.h
@@ -20,7 +20,9 @@ class EVENTS_EXPORT EventProcessor : public EventDispatcherDelegate {
virtual EventTarget* GetRootTarget() = 0;
// Dispatches an event received from the EventSource to the tree of
- // EventTargets (whose root is returned by GetRootTarget()).
+ // 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.
virtual EventDispatchDetails OnEventFromSource(Event* event)
WARN_UNUSED_RESULT;
diff --git a/chromium/ui/events/event_processor_unittest.cc b/chromium/ui/events/event_processor_unittest.cc
index 841cdb75038..048a4cdeae9 100644
--- a/chromium/ui/events/event_processor_unittest.cc
+++ b/chromium/ui/events/event_processor_unittest.cc
@@ -2,12 +2,18 @@
// 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 {
@@ -41,7 +47,7 @@ TEST_F(EventProcessorTest, Basic) {
root()->AddChild(child.Pass());
MouseEvent mouse(ET_MOUSE_MOVED, gfx::Point(10, 10), gfx::Point(10, 10),
- EF_NONE);
+ EF_NONE, EF_NONE);
DispatchEvent(&mouse);
EXPECT_TRUE(root()->child_at(0)->DidReceiveEvent(ET_MOUSE_MOVED));
EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
@@ -130,7 +136,8 @@ TEST_F(EventProcessorTest, Bounds) {
// 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);
+ 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));
@@ -141,9 +148,11 @@ TEST_F(EventProcessorTest, Bounds) {
// 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(&mouse);
+ 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));
@@ -151,7 +160,7 @@ TEST_F(EventProcessorTest, Bounds) {
parent_r->ResetReceivedEvents();
MouseEvent second(ET_MOUSE_MOVED, gfx::Point(12, 12), gfx::Point(12, 12),
- EF_NONE);
+ EF_NONE, EF_NONE);
DispatchEvent(&second);
EXPECT_FALSE(root()->DidReceiveEvent(ET_MOUSE_MOVED));
EXPECT_FALSE(parent_r->DidReceiveEvent(ET_MOUSE_MOVED));
@@ -159,5 +168,207 @@ TEST_F(EventProcessorTest, Bounds) {
EXPECT_TRUE(grandchild_r->DidReceiveEvent(ET_MOUSE_MOVED));
}
+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, 0, false);
+ 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, 0, false);
+ 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, 0, false);
+ 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, 0, false);
+ 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/chromium/ui/events/event_rewriter.h b/chromium/ui/events/event_rewriter.h
new file mode 100644
index 00000000000..f948725709a
--- /dev/null
+++ b/chromium/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/chromium/ui/events/event_rewriter_unittest.cc b/chromium/ui/events/event_rewriter_unittest.cc
new file mode 100644
index 00000000000..11a05c9c157
--- /dev/null
+++ b/chromium/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/chromium/ui/events/event_source.cc b/chromium/ui/events/event_source.cc
index 4946ea0dfd1..0f3dfb80a0a 100644
--- a/chromium/ui/events/event_source.cc
+++ b/chromium/ui/events/event_source.cc
@@ -4,16 +4,73 @@
#include "ui/events/event_source.h"
+#include <algorithm>
+
#include "ui/events/event_processor.h"
+#include "ui/events/event_rewriter.h"
namespace ui {
-void EventSource::SendEventToProcessor(Event* event) {
+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);
- EventDispatchDetails details = processor->OnEventFromSource(event);
- if (details.dispatcher_destroyed)
- return;
+ return processor->OnEventFromSource(event);
}
} // namespace ui
diff --git a/chromium/ui/events/event_source.h b/chromium/ui/events/event_source.h
index f8da9e6635e..ca8b00f514b 100644
--- a/chromium/ui/events/event_source.h
+++ b/chromium/ui/events/event_source.h
@@ -5,23 +5,43 @@
#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:
- virtual ~EventSource() {}
+ 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:
- void SendEventToProcessor(Event* event);
+ 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
diff --git a/chromium/ui/events/event_switches.cc b/chromium/ui/events/event_switches.cc
index 195e2bbdb4f..d5109700a9d 100644
--- a/chromium/ui/events/event_switches.cc
+++ b/chromium/ui/events/event_switches.cc
@@ -20,6 +20,18 @@ const char kTouchEventsEnabled[] = "enabled";
// disabled: touch events are disabled.
const char kTouchEventsDisabled[] = "disabled";
+// Enable the unified gesture detector, instead of the aura gesture detector.
+const char kUnifiedGestureDetector[] = "unified-gesture-detector";
+
+// The values the kUnifiedGestureDetector switch may have, as in
+// --unified-gesture-detector=disabled.
+// auto: Same as disabled.
+const char kUnifiedGestureDetectorAuto[] = "auto";
+// enabled: Use the unified gesture detector.
+const char kUnifiedGestureDetectorEnabled[] = "enabled";
+// disabled: Use the aura gesture detector.
+const char kUnifiedGestureDetectorDisabled[] = "disabled";
+
#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
@@ -27,4 +39,9 @@ const char kTouchEventsDisabled[] = "disabled";
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/chromium/ui/events/event_switches.h b/chromium/ui/events/event_switches.h
index d4343046e50..6697d3a7516 100644
--- a/chromium/ui/events/event_switches.h
+++ b/chromium/ui/events/event_switches.h
@@ -15,11 +15,19 @@ 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 kUnifiedGestureDetector[];
+EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetectorAuto[];
+EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetectorEnabled[];
+EVENTS_BASE_EXPORT extern const char kUnifiedGestureDetectorDisabled[];
#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/chromium/ui/events/event_target.h b/chromium/ui/events/event_target.h
index fd03667da95..3ffec550048 100644
--- a/chromium/ui/events/event_target.h
+++ b/chromium/ui/events/event_target.h
@@ -77,11 +77,13 @@ class EVENTS_EXPORT EventTarget : public EventHandler {
// Returns true if the event pre target list is empty.
bool IsPreTargetListEmpty() const;
- protected:
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;
diff --git a/chromium/ui/events/event_targeter.cc b/chromium/ui/events/event_targeter.cc
index 41d44426596..e76b5892197 100644
--- a/chromium/ui/events/event_targeter.cc
+++ b/chromium/ui/events/event_targeter.cc
@@ -32,16 +32,18 @@ EventTarget* EventTargeter::FindTargetForLocatedEvent(EventTarget* root,
EventTarget* target = root;
EventTarget* child = NULL;
while ((child = iter->GetNextTarget())) {
- if (!SubtreeShouldBeExploredForEvent(child, *event))
+ EventTargeter* targeter = child->GetEventTargeter();
+ if (!targeter)
+ targeter = this;
+ if (!targeter->SubtreeShouldBeExploredForEvent(child, *event))
continue;
target->ConvertEventToTarget(child, event);
- EventTargeter* targeter = child->GetEventTargeter();
+ target = child;
EventTarget* child_target = targeter ?
targeter->FindTargetForLocatedEvent(child, event) :
FindTargetForLocatedEvent(child, event);
if (child_target)
return child_target;
- target = child;
}
target->ConvertEventToTarget(root, event);
}
@@ -50,6 +52,22 @@ EventTarget* EventTargeter::FindTargetForLocatedEvent(EventTarget* root,
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;
}
diff --git a/chromium/ui/events/event_targeter.h b/chromium/ui/events/event_targeter.h
index a28662b5e83..dead49f1367 100644
--- a/chromium/ui/events/event_targeter.h
+++ b/chromium/ui/events/event_targeter.h
@@ -26,18 +26,43 @@ class EVENTS_EXPORT EventTargeter {
// 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. chnage the
- // coordinate to be in the returned target's coordinate sustem) so that it can
+ // 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 farther modification.
virtual EventTarget* FindTargetForLocatedEvent(EventTarget* root,
LocatedEvent* event);
- protected:
- // Returns true of |target| or one of its descendants can be a target of
- // |event|. Note that the location etc. of |event| is in |target|'s parent's
- // coordinate system.
+ // 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|.
+ // 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
diff --git a/chromium/ui/events/event_unittest.cc b/chromium/ui/events/event_unittest.cc
index 012fc608b66..186677969cb 100644
--- a/chromium/ui/events/event_unittest.cc
+++ b/chromium/ui/events/event_unittest.cc
@@ -58,17 +58,17 @@ TEST(EventTest, GetCharacter) {
TEST(EventTest, ClickCount) {
const gfx::Point origin(0, 0);
- MouseEvent mouseev(ET_MOUSE_PRESSED, origin, origin, 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, Repeated) {
+TEST(EventTest, RepeatedClick) {
const gfx::Point origin(0, 0);
- MouseEvent mouse_ev1(ET_MOUSE_PRESSED, origin, origin, 0);
- MouseEvent mouse_ev2(ET_MOUSE_PRESSED, origin, origin, 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);
@@ -98,6 +98,68 @@ TEST(EventTest, Repeated) {
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;
@@ -331,4 +393,66 @@ TEST(EventTest, KeyEventCode) {
#endif // OS_WIN
}
+#if defined(USE_X11) || defined(OS_WIN)
+TEST(EventTest, AutoRepeat) {
+ KeycodeConverter* conv = KeycodeConverter::GetInstance();
+
+ const uint16 kNativeCodeA = conv->CodeToNativeKeycode("KeyA");
+ const uint16 kNativeCodeB = conv->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, false);
+ EXPECT_FALSE(key_a1.IsRepeat());
+ KeyEvent key_a1_released(native_event_a_released, false);
+ EXPECT_FALSE(key_a1_released.IsRepeat());
+
+ KeyEvent key_a2(native_event_a_pressed, false);
+ EXPECT_FALSE(key_a2.IsRepeat());
+ KeyEvent key_a2_repeated(native_event_a_pressed, false);
+ EXPECT_TRUE(key_a2_repeated.IsRepeat());
+ KeyEvent key_a2_released(native_event_a_released, false);
+ EXPECT_FALSE(key_a2_released.IsRepeat());
+
+ KeyEvent key_a3(native_event_a_pressed, false);
+ EXPECT_FALSE(key_a3.IsRepeat());
+ KeyEvent key_b(native_event_b_pressed, false);
+ EXPECT_FALSE(key_b.IsRepeat());
+ KeyEvent key_a3_again(native_event_a_pressed, false);
+ EXPECT_FALSE(key_a3_again.IsRepeat());
+ KeyEvent key_a3_repeated(native_event_a_pressed, false);
+ EXPECT_TRUE(key_a3_repeated.IsRepeat());
+ KeyEvent key_a3_repeated2(native_event_a_pressed, false);
+ EXPECT_TRUE(key_a3_repeated2.IsRepeat());
+ KeyEvent key_a3_released(native_event_a_released, false);
+ EXPECT_FALSE(key_a3_released.IsRepeat());
+
+#if defined(USE_X11)
+ KeyEvent key_a4_pressed(native_event_a_pressed, false);
+ EXPECT_FALSE(key_a4_pressed.IsRepeat());
+
+ KeyEvent key_a4_pressed_nonstandard_state(
+ native_event_a_pressed_nonstandard_state, false);
+ EXPECT_FALSE(key_a4_pressed_nonstandard_state.IsRepeat());
+#endif
+}
+#endif // USE_X11 || OS_WIN
+
} // namespace ui
diff --git a/chromium/ui/events/event_utils.cc b/chromium/ui/events/event_utils.cc
index a8c95da6f8f..e4b384a4744 100644
--- a/chromium/ui/events/event_utils.cc
+++ b/chromium/ui/events/event_utils.cc
@@ -16,8 +16,51 @@ namespace {
int g_custom_event_types = ET_LAST;
} // namespace
-bool EventCanceledDefaultHandling(const Event& event) {
- return event.phase() == EP_POSTTARGET && event.result() != ER_UNHANDLED;
+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, false));
+ 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();
}
int RegisterCustomEventType() {
@@ -30,18 +73,22 @@ base::TimeDelta EventTimeForNow() {
}
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 false;
+ 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) {
- const gfx::Display& display = *it;
- if (display.IsInternal() &&
- display.touch_support() == gfx::Display::TOUCH_SUPPORT_AVAILABLE)
- return true;
+ if (it->IsInternal())
+ return it->touch_support();
}
- return false;
+ return gfx::Display::TOUCH_SUPPORT_UNAVAILABLE;
}
} // namespace ui
diff --git a/chromium/ui/events/event_utils.h b/chromium/ui/events/event_utils.h
index e67a74111d6..87b720d5884 100644
--- a/chromium/ui/events/event_utils.h
+++ b/chromium/ui/events/event_utils.h
@@ -6,8 +6,10 @@
#define UI_EVENTS_EVENT_UTILS_H_
#include "base/event_types.h"
+#include "base/memory/scoped_ptr.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"
@@ -31,6 +33,11 @@ class Event;
// 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);
@@ -48,7 +55,8 @@ 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/root_window_host*.
+// 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);
@@ -74,8 +82,9 @@ EVENTS_EXPORT KeyboardCode KeyboardCodeFromNative(
EVENTS_EXPORT const char* CodeFromNative(
const base::NativeEvent& native_event);
-// Returns true if the message is a mouse event.
-EVENTS_EXPORT bool IsMouseEvent(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 the flags of the button that changed during a press/release.
EVENTS_EXPORT int GetChangedMouseButtonFlagsFromNative(
@@ -85,6 +94,15 @@ EVENTS_EXPORT int GetChangedMouseButtonFlagsFromNative(
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);
@@ -124,26 +142,17 @@ EVENTS_EXPORT bool GetGestureTimes(const base::NativeEvent& native_event,
double* start_time,
double* end_time);
-// Enable/disable natural scrolling for touchpads.
-EVENTS_EXPORT void SetNaturalScroll(bool enabled);
-
-// In natural scrolling enabled for touchpads?
-EVENTS_EXPORT bool IsNaturalScrollEnabled();
-
// 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();
+
// Was this event generated by a touchpad device?
// The caller is responsible for ensuring that this is a mouse/touchpad event
// before calling this function.
EVENTS_EXPORT bool IsTouchpadEvent(const base::NativeEvent& event);
-// Returns true if event is noop.
-EVENTS_EXPORT bool IsNoopEvent(const base::NativeEvent& event);
-
-// Creates and returns no-op event.
-EVENTS_EXPORT base::NativeEvent CreateNoopEvent();
-
#if defined(OS_WIN)
EVENTS_EXPORT int GetModifiersFromACCEL(const ACCEL& accel);
EVENTS_EXPORT int GetModifiersFromKeyState();
@@ -156,11 +165,8 @@ EVENTS_EXPORT bool IsMouseEventFromTouch(UINT message);
// representing an extended key contains 0xE000 bits.
EVENTS_EXPORT uint16 GetScanCodeFromLParam(LPARAM lParam);
EVENTS_EXPORT LPARAM GetLParamFromScanCode(uint16 scan_code);
-#endif
-// Returns true if default post-target handling was canceled for |event| after
-// its dispatch to its target.
-EVENTS_EXPORT bool EventCanceledDefaultHandling(const Event& event);
+#endif
// Registers a custom event type.
EVENTS_EXPORT int RegisterCustomEventType();
diff --git a/chromium/ui/events/events.gyp b/chromium/ui/events/events.gyp
index b7a43136822..890708dab2f 100644
--- a/chromium/ui/events/events.gyp
+++ b/chromium/ui/events/events.gyp
@@ -25,21 +25,27 @@
'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': [
- 'events_base_export.h',
+ 'event_constants.h',
'event_switches.cc',
'event_switches.h',
+ 'events_base_export.h',
+ 'gesture_event_details.cc',
+ 'gesture_event_details.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_gtk.cc',
- 'keycodes/keyboard_code_conversion_gtk.h',
'keycodes/keyboard_code_conversion_mac.h',
'keycodes/keyboard_code_conversion_mac.mm',
'keycodes/keyboard_code_conversion_win.cc',
@@ -49,17 +55,18 @@
'keycodes/keyboard_codes.h',
'latency_info.cc',
'latency_info.h',
- 'x/device_list_cache_x.cc',
- 'x/device_list_cache_x.h',
'x/device_data_manager.cc',
'x/device_data_manager.h',
+ 'x/device_list_cache_x.cc',
+ 'x/device_list_cache_x.h',
'x/touch_factory_x11.cc',
'x/touch_factory_x11.h',
],
'conditions': [
['use_x11==1', {
'dependencies': [
- '<(DEPTH)/build/linux/system.gyp:x11',
+ '../../build/linux/system.gyp:x11',
+ '../gfx/x/gfx_x11.gyp:gfx_x11',
],
}],
],
@@ -72,21 +79,26 @@
'<(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': [
+ 'cocoa/cocoa_event_utils.h',
+ 'cocoa/cocoa_event_utils.mm',
+ 'cocoa/events_mac.mm',
'event.cc',
'event.h',
- 'event_constants.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',
@@ -98,54 +110,113 @@
'event_utils.h',
'events_export.h',
'events_stub.cc',
- 'gestures/gesture_configuration.cc',
- 'gestures/gesture_configuration.h',
'gestures/gesture_point.cc',
'gestures/gesture_point.h',
+ '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_sequence.cc',
'gestures/gesture_sequence.h',
- 'gestures/gesture_types.cc',
'gestures/gesture_types.h',
- 'gestures/gesture_util.cc',
- 'gestures/gesture_util.h',
+ 'gestures/motion_event_aura.cc',
+ 'gestures/motion_event_aura.h',
+ 'gestures/unified_gesture_detector_enabled.cc',
+ 'gestures/unified_gesture_detector_enabled.h',
'gestures/velocity_calculator.cc',
'gestures/velocity_calculator.h',
- 'ozone/evdev/event_device_info.cc',
- 'ozone/evdev/event_device_info.h',
- 'ozone/evdev/event_factory.cc',
- 'ozone/evdev/event_factory.h',
- 'ozone/evdev/event_modifiers.cc',
- 'ozone/evdev/event_modifiers.h',
- 'ozone/evdev/key_event_converter.cc',
- 'ozone/evdev/key_event_converter.h',
- 'ozone/evdev/touch_event_converter.cc',
- 'ozone/evdev/touch_event_converter.h',
- 'ozone/event_converter_ozone.cc',
- 'ozone/event_converter_ozone.h',
- 'ozone/event_factory_ozone.cc',
- 'ozone/event_factory_ozone.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_aura==0', {
+ 'sources!': [
+ 'gestures/gesture_point.cc',
+ 'gestures/gesture_point.h',
+ '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_sequence.cc',
+ 'gestures/gesture_sequence.h',
+ 'gestures/gesture_types.h',
+ 'gestures/motion_event_aura.cc',
+ 'gestures/motion_event_aura.h',
+ 'gestures/velocity_calculator.cc',
+ 'gestures/velocity_calculator.h',
+ ],
+ }],
# We explicitly enumerate the platforms we _do_ provide native cracking
# for here.
- ['OS=="win" or use_x11==1 or use_ozone==1', {
+ ['OS=="win" or OS=="mac" or use_x11==1 or use_ozone==1', {
'sources!': [
'events_stub.cc',
],
}],
- ['use_x11==1', {
- 'dependencies': [
- '<(DEPTH)/build/linux/system.gyp:x11',
+ ['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',
],
}],
- ['use_ozone_evdev==1', {
- 'defines': ['USE_OZONE_EVDEV=1'],
+ ],
+ },
+ {
+ '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': [
+ '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_provider.cc',
+ 'gesture_detection/gesture_provider.h',
+ 'gesture_detection/motion_event.h',
+ 'gesture_detection/scale_gesture_detector.cc',
+ 'gesture_detection/scale_gesture_detector.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',
+ ],
}],
],
},
@@ -153,14 +224,22 @@
'target_name': 'events_test_support',
'type': 'static_library',
'dependencies': [
+ '<(DEPTH)/skia/skia.gyp:skia',
'events',
'events_base',
+ 'platform/events_platform.gyp:events_platform',
],
'sources': [
+ 'test/cocoa_test_event_utils.h',
+ 'test/cocoa_test_event_utils.mm',
'test/events_test_utils.cc',
'test/events_test_utils.h',
'test/events_test_utils_x11.cc',
'test/events_test_utils_x11.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',
@@ -169,9 +248,14 @@
'conditions': [
['use_x11==1', {
'dependencies': [
- '<(DEPTH)/build/linux/system.gyp:x11',
+ '../../build/linux/system.gyp:x11',
+ '../gfx/x/gfx_x11.gyp:gfx_x11',
],
}],
+ ['OS=="ios"', {
+ # The cocoa files don't apply to iOS.
+ 'sources/': [['exclude', 'cocoa']],
+ }],
],
},
{
@@ -179,35 +263,90 @@
'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_base',
'events',
+ 'events_base',
'events_test_support',
+ 'gesture_detection',
+ 'platform/events_platform.gyp:events_platform',
],
'sources': [
+ 'cocoa/events_mac_unittest.mm',
'event_dispatcher_unittest.cc',
'event_processor_unittest.cc',
+ 'event_rewriter_unittest.cc',
'event_unittest.cc',
+ 'gestures/motion_event_aura_unittest.cc',
'gestures/velocity_calculator_unittest.cc',
+ 'gesture_detection/bitset_32_unittest.cc',
+ 'gesture_detection/gesture_provider_unittest.cc',
+ 'gesture_detection/mock_motion_event.h',
+ 'gesture_detection/mock_motion_event.cc',
+ 'gesture_detection/velocity_tracker_unittest.cc',
+ 'gesture_detection/touch_disposition_gesture_filter_unittest.cc',
'keycodes/dom4/keycode_converter_unittest.cc',
'latency_info_unittest.cc',
- 'test/run_all_unittests.cc',
- 'test/test_suite.cc',
- 'test/test_suite.h',
- 'ozone/evdev/key_event_converter_unittest.cc',
- 'ozone/evdev/touch_event_converter_unittest.cc',
+ 'platform/platform_event_source_unittest.cc',
'x/events_x_unittest.cc',
],
'conditions': [
- ['OS=="linux" and linux_use_tcmalloc==1', {
+ ['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/motion_event_aura_unittest.cc',
+ 'gestures/velocity_calculator_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/chromium/ui/events/events_stub.cc b/chromium/ui/events/events_stub.cc
index 3d63e4ddf63..2ce911361c1 100644
--- a/chromium/ui/events/events_stub.cc
+++ b/chromium/ui/events/events_stub.cc
@@ -4,6 +4,7 @@
#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"
@@ -48,11 +49,6 @@ int EventButtonFromNative(const base::NativeEvent& native_event) {
return 0;
}
-bool IsMouseEvent(const base::NativeEvent& native_event) {
- NOTIMPLEMENTED();
- return false;
-}
-
int GetChangedMouseButtonFlagsFromNative(
const base::NativeEvent& native_event) {
NOTIMPLEMENTED();
@@ -64,6 +60,15 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
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 ClearTouchIdIfReleased(const base::NativeEvent& native_event) {
NOTIMPLEMENTED();
}
@@ -134,15 +139,6 @@ bool IsTouchpadEvent(const base::NativeEvent& native_event) {
return false;
}
-bool IsNoopEvent(const base::NativeEvent& native_event) {
- NOTIMPLEMENTED();
- return false;
-}
-
-base::NativeEvent CreateNoopEvent() {
- return base::NativeEvent();
-}
-
KeyboardCode KeyboardCodeFromNative(const base::NativeEvent& native_event) {
NOTIMPLEMENTED();
return static_cast<KeyboardCode>(0);
@@ -153,4 +149,9 @@ const char* CodeFromNative(const base::NativeEvent& native_event) {
return "";
}
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/OWNERS b/chromium/ui/events/gesture_detection/OWNERS
new file mode 100644
index 00000000000..3e9a72974f2
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/OWNERS
@@ -0,0 +1,2 @@
+jdduke@chromium.org
+tdresser@chromium.org
diff --git a/chromium/ui/events/gesture_detection/bitset_32.h b/chromium/ui/events/gesture_detection/bitset_32.h
new file mode 100644
index 00000000000..9cb9d48aee6
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/bitset_32.h
@@ -0,0 +1,128 @@
+// 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"
+
+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) { 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 {
+ 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/chromium/ui/events/gesture_detection/bitset_32_unittest.cc b/chromium/ui/events/gesture_detection/bitset_32_unittest.cc
new file mode 100644
index 00000000000..2e5d2711e02
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/filtered_gesture_provider.cc b/chromium/ui/events/gesture_detection/filtered_gesture_provider.cc
new file mode 100644
index 00000000000..76ee5201d1a
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/filtered_gesture_provider.h b/chromium/ui/events/gesture_detection/filtered_gesture_provider.h
new file mode 100644
index 00000000000..4d63be64a83
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/gesture_config_helper.cc b/chromium/ui/events/gesture_detection/gesture_config_helper.cc
new file mode 100644
index 00000000000..0039e2c1f93
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/gesture_config_helper.h b/chromium/ui/events/gesture_detection/gesture_config_helper.h
new file mode 100644
index 00000000000..0b54c368786
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/gesture_config_helper_android.cc b/chromium/ui/events/gesture_detection/gesture_config_helper_android.cc
new file mode 100644
index 00000000000..546cb84e119
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_config_helper_android.cc
@@ -0,0 +1,73 @@
+// 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;
+
+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;
+
+ config.gesture_detector_config = DefaultGestureDetectorConfig(display);
+ config.quick_scale_enabled = true;
+
+ const float px_to_dp = 1.f / display.device_scale_factor();
+ config.min_scaling_touch_major =
+ ViewConfiguration::GetMinScalingTouchMajorInPixels() * px_to_dp;
+ config.min_scaling_span =
+ ViewConfiguration::GetMinScalingSpanInPixels() * px_to_dp;
+
+ 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;
+ return config;
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc b/chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc
new file mode 100644
index 00000000000..36be122b772
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_config_helper_aura.cc
@@ -0,0 +1,73 @@
+// 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_config_helper.h"
+
+#include <cmath>
+
+#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 =
+ atan2(1.f, GestureConfiguration::max_swipe_deviation_ratio()) * 180.0f /
+ static_cast<float>(M_PI);
+ 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;
+
+ config.gesture_detector_config = DefaultGestureDetectorConfig();
+ 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 =
+ GestureConfiguration::min_pinch_update_distance_in_pixels();
+ return config;
+}
+
+} // namespace
+
+GestureProvider::Config DefaultGestureProviderConfig() {
+ GestureProvider::Config config;
+ config.display = gfx::Screen::GetNativeScreen()->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/chromium/ui/events/gesture_detection/gesture_detection_export.h b/chromium/ui/events/gesture_detection/gesture_detection_export.h
new file mode 100644
index 00000000000..edef5244be3
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/gesture_detector.cc b/chromium/ui/events/gesture_detection/gesture_detector.cc
new file mode 100644
index 00000000000..5dc61938b1a
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_detector.cc
@@ -0,0 +1,553 @@
+// 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/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() {}
+
+bool GestureDetector::SimpleGestureListener::OnDown(const MotionEvent& e) {
+ return false;
+}
+
+void GestureDetector::SimpleGestureListener::OnShowPress(const MotionEvent& e) {
+}
+
+bool GestureDetector::SimpleGestureListener::OnSingleTapUp(
+ const MotionEvent& e) {
+ return false;
+}
+
+void GestureDetector::SimpleGestureListener::OnLongPress(const MotionEvent& e) {
+}
+
+bool GestureDetector::SimpleGestureListener::OnScroll(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float distance_x,
+ float distance_y) {
+ return false;
+}
+
+bool GestureDetector::SimpleGestureListener::OnFling(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float velocity_x,
+ float velocity_y) {
+ return false;
+}
+
+bool GestureDetector::SimpleGestureListener::OnSwipe(const MotionEvent& e1,
+ const MotionEvent& e2,
+ float velocity_x,
+ float velocity_y) {
+ return false;
+}
+
+bool GestureDetector::SimpleGestureListener::OnTwoFingerTap(
+ const MotionEvent& e1,
+ const MotionEvent& e2) {
+ return false;
+}
+
+bool GestureDetector::SimpleGestureListener::OnSingleTapConfirmed(
+ const MotionEvent& e) {
+ return false;
+}
+
+bool GestureDetector::SimpleGestureListener::OnDoubleTap(const MotionEvent& e) {
+ return false;
+}
+
+bool GestureDetector::SimpleGestureListener::OnDoubleTapEvent(
+ const MotionEvent& e) {
+ return false;
+}
+
+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),
+ 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.
+ 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/chromium/ui/events/gesture_detection/gesture_detector.h b/chromium/ui/events/gesture_detection/gesture_detector.h
new file mode 100644
index 00000000000..db665f8cbcd
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_detector.h
@@ -0,0 +1,214 @@
+// 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 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 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;
+ };
+
+ class 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;
+ };
+
+ class 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 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;
+ };
+
+ 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; }
+
+ 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 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/chromium/ui/events/gesture_detection/gesture_event_data.cc b/chromium/ui/events/gesture_detection/gesture_event_data.cc
new file mode 100644
index 00000000000..42d1d009992
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_event_data.cc
@@ -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.
+
+#include "ui/events/gesture_detection/gesture_event_data.h"
+
+#include "base/logging.h"
+
+namespace ui {
+
+GestureEventData::GestureEventData(const GestureEventDetails& details,
+ int motion_event_id,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box)
+ : details(details),
+ motion_event_id(motion_event_id),
+ time(time),
+ x(x),
+ y(y),
+ raw_x(raw_x),
+ raw_y(raw_y) {
+ 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, 0, 0),
+ motion_event_id(other.motion_event_id),
+ time(other.time),
+ x(other.x),
+ y(other.y),
+ raw_x(other.raw_x),
+ raw_y(other.raw_y) {
+ details.set_touch_points(other.details.touch_points());
+ details.set_bounding_box(other.details.bounding_box_f());
+}
+
+GestureEventData::GestureEventData()
+ : motion_event_id(0), x(0), y(0), raw_x(0), raw_y(0) {
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/gesture_event_data.h b/chromium/ui/events/gesture_detection/gesture_event_data.h
new file mode 100644
index 00000000000..35eb41e2c77
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_event_data.h
@@ -0,0 +1,48 @@
+// 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_event_details.h"
+
+namespace ui {
+
+class GestureEventDataPacket;
+
+struct GESTURE_DETECTION_EXPORT GestureEventData {
+ GestureEventData(const GestureEventDetails&,
+ int motion_event_id,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box);
+ GestureEventData(EventType type, const GestureEventData&);
+
+ EventType type() const { return details.type(); }
+
+ GestureEventDetails details;
+ int motion_event_id;
+ base::TimeTicks time;
+ float x;
+ float y;
+ float raw_x;
+ float raw_y;
+
+ 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/chromium/ui/events/gesture_detection/gesture_event_data_packet.cc b/chromium/ui/events/gesture_detection/gesture_event_data_packet.cc
new file mode 100644
index 00000000000..09adceca475
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/gesture_event_data_packet.h b/chromium/ui/events/gesture_detection/gesture_event_data_packet.h
new file mode 100644
index 00000000000..0c40f576784
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_event_data_packet.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_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_
+#define UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DATA_PACKET_H_
+
+#include <vector>
+
+#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);
+
+ base::TimeTicks timestamp_;
+ // TODO(jdduke): This vector is in general very short. Optimize?
+ std::vector<GestureEventData> 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/chromium/ui/events/gesture_detection/gesture_provider.cc b/chromium/ui/events/gesture_detection/gesture_provider.cc
new file mode 100644
index 00000000000..0255ac74bac
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_provider.cc
@@ -0,0 +1,810 @@
+// 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/motion_event.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 GetBoundingBox(const MotionEvent& event) {
+ gfx::RectF bounds;
+ for (size_t i = 0; i < event.GetPointerCount(); ++i) {
+ float diameter = event.GetTouchMajor(i);
+ bounds.Union(gfx::RectF(event.GetX(i) - diameter / 2,
+ event.GetY(i) - diameter / 2,
+ diameter,
+ diameter));
+ }
+ return bounds;
+}
+
+GestureEventData CreateGesture(const GestureEventDetails& details,
+ int motion_event_id,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box) {
+ return GestureEventData(details,
+ motion_event_id,
+ time,
+ x,
+ y,
+ raw_x,
+ raw_y,
+ touch_point_count,
+ bounding_box);
+}
+
+GestureEventData CreateGesture(EventType type,
+ int motion_event_id,
+ base::TimeTicks time,
+ float x,
+ float y,
+ float raw_x,
+ float raw_y,
+ size_t touch_point_count,
+ const gfx::RectF& bounding_box) {
+ return GestureEventData(GestureEventDetails(type, 0, 0),
+ motion_event_id,
+ time,
+ x,
+ y,
+ raw_x,
+ raw_y,
+ touch_point_count,
+ bounding_box);
+}
+
+GestureEventData CreateGesture(const GestureEventDetails& details,
+ const MotionEvent& event) {
+ return GestureEventData(details,
+ event.GetId(),
+ event.GetEventTime(),
+ event.GetX(),
+ event.GetY(),
+ event.GetRawX(),
+ event.GetRawY(),
+ event.GetPointerCount(),
+ GetBoundingBox(event));
+}
+
+GestureEventData CreateGesture(EventType type, const MotionEvent& event) {
+ return CreateGesture(GestureEventDetails(type, 0, 0), event);
+}
+
+GestureEventDetails CreateTapGestureDetails(EventType type) {
+ // 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 tap_details(type, 1, 0);
+ return tap_details;
+}
+
+} // 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) {}
+
+GestureProvider::Config::~Config() {}
+
+// GestureProvider::ScaleGestureListener
+
+class GestureProvider::ScaleGestureListenerImpl
+ : public ScaleGestureDetector::ScaleGestureListener {
+ public:
+ ScaleGestureListenerImpl(const ScaleGestureDetector::Config& config,
+ GestureProvider* provider)
+ : scale_gesture_detector_(config, this),
+ provider_(provider),
+ ignore_multitouch_events_(false),
+ pinch_event_sent_(false),
+ min_pinch_update_span_delta_(config.min_pinch_update_span_delta) {}
+
+ bool OnTouchEvent(const MotionEvent& event) {
+ // TODO: Need to deal with multi-touch transition.
+ const bool in_scale_gesture = IsScaleGestureDetectionInProgress();
+ bool handled = scale_gesture_detector_.OnTouchEvent(event);
+ if (!in_scale_gesture &&
+ (event.GetAction() == MotionEvent::ACTION_UP ||
+ event.GetAction() == MotionEvent::ACTION_CANCEL)) {
+ return false;
+ }
+ return handled;
+ }
+
+ // ScaleGestureDetector::ScaleGestureListener implementation.
+ virtual bool OnScaleBegin(const ScaleGestureDetector& detector,
+ const MotionEvent& e) OVERRIDE {
+ if (ignore_multitouch_events_ && !detector.InDoubleTapMode())
+ return false;
+ pinch_event_sent_ = false;
+ return true;
+ }
+
+ virtual void OnScaleEnd(const ScaleGestureDetector& detector,
+ const MotionEvent& e) OVERRIDE {
+ if (!pinch_event_sent_)
+ return;
+ provider_->Send(CreateGesture(ET_GESTURE_PINCH_END, e));
+ pinch_event_sent_ = false;
+ }
+
+ virtual bool OnScale(const ScaleGestureDetector& detector,
+ const MotionEvent& e) OVERRIDE {
+ if (ignore_multitouch_events_ && !detector.InDoubleTapMode())
+ return false;
+ if (!pinch_event_sent_) {
+ pinch_event_sent_ = true;
+ provider_->Send(CreateGesture(ET_GESTURE_PINCH_BEGIN,
+ e.GetId(),
+ detector.GetEventTime(),
+ detector.GetFocusX(),
+ detector.GetFocusY(),
+ detector.GetFocusX() + e.GetRawOffsetX(),
+ detector.GetFocusY() + e.GetRawOffsetY(),
+ e.GetPointerCount(),
+ GetBoundingBox(e)));
+ }
+
+ if (std::abs(detector.GetCurrentSpan() - detector.GetPreviousSpan()) <
+ 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, scale, 0);
+ provider_->Send(CreateGesture(pinch_details,
+ e.GetId(),
+ detector.GetEventTime(),
+ detector.GetFocusX(),
+ detector.GetFocusY(),
+ detector.GetFocusX() + e.GetRawOffsetX(),
+ detector.GetFocusY() + e.GetRawOffsetY(),
+ e.GetPointerCount(),
+ GetBoundingBox(e)));
+ return true;
+ }
+
+ void SetDoubleTapEnabled(bool enabled) {
+ DCHECK(!IsDoubleTapInProgress());
+ scale_gesture_detector_.SetQuickScaleEnabled(enabled);
+ }
+
+ void SetMultiTouchEnabled(bool enabled) {
+ // Note that returning false from OnScaleBegin / OnScale makes the
+ // gesture detector not to emit further scaling notifications
+ // related to this gesture. Thus, if detector events are enabled in
+ // the middle of the gesture, we don't need to do anything.
+ ignore_multitouch_events_ = !enabled;
+ }
+
+ bool IsDoubleTapInProgress() const {
+ return IsScaleGestureDetectionInProgress() && InDoubleTapMode();
+ }
+
+ bool IsScaleGestureDetectionInProgress() const {
+ return scale_gesture_detector_.IsInProgress();
+ }
+
+ private:
+ bool InDoubleTapMode() const {
+ return scale_gesture_detector_.InDoubleTapMode();
+ }
+
+ ScaleGestureDetector scale_gesture_detector_;
+
+ GestureProvider* const provider_;
+
+ // Completely silence multi-touch (pinch) scaling events. Used in WebView when
+ // zoom support is turned off.
+ bool ignore_multitouch_events_;
+
+ // Whether any pinch zoom event has been sent to native.
+ bool pinch_event_sent_;
+
+ // The minimum change in span required before this is considered a pinch. See
+ // crbug.com/373318.
+ float min_pinch_update_span_delta_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScaleGestureListenerImpl);
+};
+
+// GestureProvider::GestureListener
+
+class GestureProvider::GestureListenerImpl
+ : public GestureDetector::GestureListener,
+ public GestureDetector::DoubleTapListener {
+ public:
+ GestureListenerImpl(
+ const gfx::Display& display,
+ const GestureDetector::Config& gesture_detector_config,
+ bool disable_click_delay,
+ GestureProvider* provider)
+ : gesture_detector_(gesture_detector_config, this, this),
+ snap_scroll_controller_(display),
+ provider_(provider),
+ disable_click_delay_(disable_click_delay),
+ touch_slop_(gesture_detector_config.touch_slop),
+ double_tap_timeout_(gesture_detector_config.double_tap_timeout),
+ ignore_single_tap_(false),
+ seen_first_scroll_event_(false) {}
+
+ virtual ~GestureListenerImpl() {}
+
+ bool OnTouchEvent(const MotionEvent& e,
+ bool is_scale_gesture_detection_in_progress) {
+ snap_scroll_controller_.SetSnapScrollingMode(
+ e, is_scale_gesture_detection_in_progress);
+
+ if (is_scale_gesture_detection_in_progress)
+ SetIgnoreSingleTap(true);
+
+ if (e.GetAction() == MotionEvent::ACTION_DOWN)
+ gesture_detector_.set_longpress_enabled(true);
+
+ return gesture_detector_.OnTouchEvent(e);
+ }
+
+ // GestureDetector::GestureListener implementation.
+ virtual bool OnDown(const MotionEvent& e) OVERRIDE {
+ current_down_time_ = e.GetEventTime();
+ ignore_single_tap_ = false;
+ seen_first_scroll_event_ = false;
+
+ GestureEventDetails tap_details(ET_GESTURE_TAP_DOWN, 0, 0);
+ provider_->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 (!seen_first_scroll_event_) {
+ // Remove the touch slop region from the first scroll event to avoid a
+ // jump.
+ seen_first_scroll_event_ = true;
+ 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 - touch_slop_) / distance;
+ distance_x *= ratio;
+ distance_y *= ratio;
+ }
+ }
+ 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 (!provider_->IsScrollInProgress()) {
+ // 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.
+ provider_->Send(CreateGesture(scroll_details,
+ e2.GetId(),
+ e2.GetEventTime(),
+ e1.GetX(),
+ e1.GetY(),
+ e1.GetRawX(),
+ e1.GetRawY(),
+ e2.GetPointerCount(),
+ GetBoundingBox(e2)));
+ }
+
+ if (distance_x || distance_y) {
+ const gfx::RectF bounding_box = GetBoundingBox(e2);
+ const gfx::PointF center = bounding_box.CenterPoint();
+ const gfx::PointF raw_center =
+ center + gfx::Vector2dF(e2.GetRawOffsetX(), e2.GetRawOffsetY());
+ GestureEventDetails scroll_details(
+ ET_GESTURE_SCROLL_UPDATE, -distance_x, -distance_y);
+ provider_->Send(CreateGesture(scroll_details,
+ e2.GetId(),
+ e2.GetEventTime(),
+ center.x(),
+ center.y(),
+ raw_center.x(),
+ raw_center.y(),
+ e2.GetPointerCount(),
+ bounding_box));
+ }
+
+ 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;
+ }
+ }
+
+ provider_->Fling(e2, velocity_x, velocity_y);
+ 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);
+ provider_->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());
+ provider_->Send(CreateGesture(two_finger_tap_details,
+ e2.GetId(),
+ e2.GetEventTime(),
+ e1.GetX(),
+ e1.GetY(),
+ e1.GetRawX(),
+ e1.GetRawY(),
+ e2.GetPointerCount(),
+ GetBoundingBox(e2)));
+ return true;
+ }
+
+ virtual void OnShowPress(const MotionEvent& e) OVERRIDE {
+ GestureEventDetails show_press_details(ET_GESTURE_SHOW_PRESS, 0, 0);
+ provider_->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_ > double_tap_timeout_) {
+ return OnSingleTapConfirmed(e);
+ } else if (!IsDoubleTapEnabled() || 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.
+ provider_->Send(CreateGesture(
+ CreateTapGestureDetails(ET_GESTURE_TAP_UNCONFIRMED), e));
+ }
+ }
+
+ return provider_->SendLongTapIfNecessary(e);
+ }
+
+ // GestureDetector::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;
+
+ provider_->Send(CreateGesture(CreateTapGestureDetails(ET_GESTURE_TAP), e));
+ return true;
+ }
+
+ virtual bool OnDoubleTap(const MotionEvent& e) OVERRIDE { return false; }
+
+ 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 (!provider_->IsPinchInProgress() &&
+ !provider_->IsScrollInProgress()) {
+ provider_->Send(
+ CreateGesture(CreateTapGestureDetails(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, 0, 0);
+ provider_->Send(CreateGesture(long_press_details, e));
+ }
+
+ void SetDoubleTapEnabled(bool enabled) {
+ DCHECK(!IsDoubleTapInProgress());
+ gesture_detector_.SetDoubleTapListener(enabled ? this : NULL);
+ }
+
+ bool IsDoubleTapInProgress() const {
+ return gesture_detector_.is_double_tapping();
+ }
+
+ private:
+ void SetIgnoreSingleTap(bool value) { ignore_single_tap_ = value; }
+
+ bool IsDoubleTapEnabled() const {
+ return gesture_detector_.has_doubletap_listener();
+ }
+
+ GestureDetector gesture_detector_;
+ SnapScrollController snap_scroll_controller_;
+
+ GestureProvider* const provider_;
+
+ // Whether the click delay should always be disabled by sending clicks for
+ // double-tap gestures.
+ const bool disable_click_delay_;
+
+ const float touch_slop_;
+
+ const base::TimeDelta double_tap_timeout_;
+
+ base::TimeTicks current_down_time_;
+
+ // 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_;
+
+ // Used to remove the touch slop from the initial scroll event in a scroll
+ // gesture.
+ bool seen_first_scroll_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(GestureListenerImpl);
+};
+
+// GestureProvider
+
+GestureProvider::GestureProvider(const Config& config,
+ GestureProviderClient* client)
+ : client_(client),
+ touch_scroll_in_progress_(false),
+ pinch_in_progress_(false),
+ double_tap_support_for_page_(true),
+ double_tap_support_for_platform_(true),
+ gesture_begin_end_types_enabled_(config.gesture_begin_end_types_enabled),
+ min_gesture_bounds_length_(config.min_gesture_bounds_length) {
+ DCHECK(client);
+ InitGestureDetectors(config);
+}
+
+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;
+
+ const bool in_scale_gesture =
+ scale_gesture_listener_->IsScaleGestureDetectionInProgress();
+
+ OnTouchEventHandlingBegin(event);
+ gesture_listener_->OnTouchEvent(event, in_scale_gesture);
+ scale_gesture_listener_->OnTouchEvent(event);
+ OnTouchEventHandlingEnd(event);
+ return true;
+}
+
+void GestureProvider::SetMultiTouchZoomSupportEnabled(bool enabled) {
+ scale_gesture_listener_->SetMultiTouchEnabled(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 {
+ // TODO(wangxianzhu): Also return true when fling is active once the UI knows
+ // exactly when the fling ends.
+ return touch_scroll_in_progress_;
+}
+
+bool GestureProvider::IsPinchInProgress() const { return pinch_in_progress_; }
+
+bool GestureProvider::IsDoubleTapInProgress() const {
+ return gesture_listener_->IsDoubleTapInProgress() ||
+ scale_gesture_listener_->IsDoubleTapInProgress();
+}
+
+void GestureProvider::InitGestureDetectors(const Config& config) {
+ TRACE_EVENT0("input", "GestureProvider::InitGestureDetectors");
+ gesture_listener_.reset(
+ new GestureListenerImpl(config.display,
+ config.gesture_detector_config,
+ config.disable_click_delay,
+ this));
+
+ scale_gesture_listener_.reset(
+ new ScaleGestureListenerImpl(config.scale_gesture_detector_config, this));
+
+ UpdateDoubleTapDetectionSupport();
+}
+
+bool GestureProvider::CanHandle(const MotionEvent& event) const {
+ return event.GetAction() == MotionEvent::ACTION_DOWN || current_down_event_;
+}
+
+void GestureProvider::Fling(const MotionEvent& event,
+ float velocity_x,
+ float velocity_y) {
+ if (!velocity_x && !velocity_y) {
+ EndTouchScrollIfNecessary(event, true);
+ return;
+ }
+
+ if (!touch_scroll_in_progress_) {
+ // The native side needs a ET_GESTURE_SCROLL_BEGIN before
+ // ET_SCROLL_FLING_START to send the fling to the correct target. Send if it
+ // has not sent. 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, event));
+ }
+ EndTouchScrollIfNecessary(event, false);
+
+ GestureEventDetails fling_details(
+ ET_SCROLL_FLING_START, velocity_x, velocity_y);
+ Send(CreateGesture(fling_details, event));
+}
+
+void GestureProvider::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_event_ || gesture.type() == ET_GESTURE_TAP ||
+ gesture.type() == ET_GESTURE_SHOW_PRESS);
+
+ // TODO(jdduke): Provide a way of skipping this clamping for stylus and/or
+ // mouse-based input, perhaps by exposing the source type on MotionEvent.
+ const gfx::RectF& gesture_bounds = gesture.details.bounding_box_f();
+ gesture.details.set_bounding_box(gfx::RectF(
+ gesture_bounds.x(),
+ gesture_bounds.y(),
+ std::max(min_gesture_bounds_length_, gesture_bounds.width()),
+ std::max(min_gesture_bounds_length_, gesture_bounds.height())));
+
+ switch (gesture.type()) {
+ case ET_GESTURE_LONG_PRESS:
+ DCHECK(!scale_gesture_listener_->IsScaleGestureDetectionInProgress());
+ current_longpress_time_ = gesture.time;
+ break;
+ case ET_GESTURE_LONG_TAP:
+ current_longpress_time_ = base::TimeTicks();
+ break;
+ case ET_GESTURE_SCROLL_BEGIN:
+ DCHECK(!touch_scroll_in_progress_);
+ touch_scroll_in_progress_ = true;
+ break;
+ case ET_GESTURE_SCROLL_END:
+ DCHECK(touch_scroll_in_progress_);
+ if (pinch_in_progress_)
+ Send(GestureEventData(ET_GESTURE_PINCH_END, gesture));
+ touch_scroll_in_progress_ = false;
+ break;
+ case ET_GESTURE_PINCH_BEGIN:
+ DCHECK(!pinch_in_progress_);
+ if (!touch_scroll_in_progress_)
+ Send(GestureEventData(ET_GESTURE_SCROLL_BEGIN, gesture));
+ pinch_in_progress_ = true;
+ break;
+ case ET_GESTURE_PINCH_END:
+ DCHECK(pinch_in_progress_);
+ pinch_in_progress_ = 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_in_progress_ || touch_scroll_in_progress_)
+ return;
+ default:
+ break;
+ };
+
+ client_->OnGestureEvent(gesture);
+}
+
+bool GestureProvider::SendLongTapIfNecessary(const MotionEvent& event) {
+ if (event.GetAction() == MotionEvent::ACTION_UP &&
+ !current_longpress_time_.is_null() &&
+ !scale_gesture_listener_->IsScaleGestureDetectionInProgress()) {
+ GestureEventDetails long_tap_details(ET_GESTURE_LONG_TAP, 0, 0);
+ Send(CreateGesture(long_tap_details, event));
+ return true;
+ }
+ return false;
+}
+
+void GestureProvider::EndTouchScrollIfNecessary(const MotionEvent& event,
+ bool send_scroll_end_event) {
+ if (!touch_scroll_in_progress_)
+ return;
+ if (send_scroll_end_event)
+ Send(CreateGesture(ET_GESTURE_SCROLL_END, event));
+ touch_scroll_in_progress_ = false;
+}
+
+void GestureProvider::OnTouchEventHandlingBegin(const MotionEvent& event) {
+ switch (event.GetAction()) {
+ case MotionEvent::ACTION_DOWN:
+ current_down_event_ = event.Clone();
+ touch_scroll_in_progress_ = false;
+ pinch_in_progress_ = false;
+ current_longpress_time_ = base::TimeTicks();
+ if (gesture_begin_end_types_enabled_)
+ Send(CreateGesture(ET_GESTURE_BEGIN, event));
+ break;
+ case MotionEvent::ACTION_POINTER_DOWN:
+ if (gesture_begin_end_types_enabled_) {
+ const int action_index = event.GetActionIndex();
+ Send(CreateGesture(ET_GESTURE_BEGIN,
+ event.GetId(),
+ event.GetEventTime(),
+ event.GetX(action_index),
+ event.GetY(action_index),
+ event.GetRawX(action_index),
+ event.GetRawY(action_index),
+ event.GetPointerCount(),
+ GetBoundingBox(event)));
+ }
+ 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: {
+ // Note: This call will have no effect if a fling was just generated, as
+ // |Fling()| will have already signalled an end to touch-scrolling.
+ EndTouchScrollIfNecessary(event, true);
+
+ const gfx::RectF bounding_box = GetBoundingBox(event);
+
+ if (gesture_begin_end_types_enabled_) {
+ for (size_t i = 0; i < event.GetPointerCount(); ++i) {
+ Send(CreateGesture(ET_GESTURE_END,
+ event.GetId(),
+ event.GetEventTime(),
+ event.GetX(i),
+ event.GetY(i),
+ event.GetRawX(i),
+ event.GetRawY(i),
+ event.GetPointerCount() - i,
+ bounding_box));
+ }
+ }
+
+ current_down_event_.reset();
+
+ UpdateDoubleTapDetectionSupport();
+ break;
+ }
+ case MotionEvent::ACTION_POINTER_UP:
+ if (gesture_begin_end_types_enabled_)
+ Send(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);
+ scale_gesture_listener_->SetDoubleTapEnabled(double_tap_enabled);
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/gesture_provider.h b/chromium/ui/events/gesture_detection/gesture_provider.h
new file mode 100644
index 00000000000..e9407c7e699
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_provider.h
@@ -0,0 +1,136 @@
+// 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/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. Defaults to false.
+ bool gesture_begin_end_types_enabled;
+
+ // The minimum 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 touch sizes.
+ // Defaults to 0.
+ float min_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:
+ void InitGestureDetectors(const Config& config);
+
+ bool CanHandle(const MotionEvent& event) const;
+
+ void Fling(const MotionEvent& e, float velocity_x, float velocity_y);
+ void Send(GestureEventData gesture);
+ bool SendLongTapIfNecessary(const MotionEvent& event);
+ void EndTouchScrollIfNecessary(const MotionEvent& event,
+ bool send_scroll_end_event);
+ void OnTouchEventHandlingBegin(const MotionEvent& event);
+ void OnTouchEventHandlingEnd(const MotionEvent& event);
+ void UpdateDoubleTapDetectionSupport();
+
+ GestureProviderClient* const client_;
+
+ class GestureListenerImpl;
+ friend class GestureListenerImpl;
+ scoped_ptr<GestureListenerImpl> gesture_listener_;
+
+ class ScaleGestureListenerImpl;
+ friend class ScaleGestureListenerImpl;
+ scoped_ptr<ScaleGestureListenerImpl> scale_gesture_listener_;
+
+ scoped_ptr<MotionEvent> current_down_event_;
+
+ // Whether the respective {SCROLL,PINCH}_BEGIN gestures have been terminated
+ // with a {SCROLL,PINCH}_END.
+ bool touch_scroll_in_progress_;
+ bool pinch_in_progress_;
+
+ // Whether double-tap gesture detection is currently supported.
+ bool double_tap_support_for_page_;
+ bool double_tap_support_for_platform_;
+
+ // 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_;
+
+ const bool gesture_begin_end_types_enabled_;
+
+ const float min_gesture_bounds_length_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_PROVIDER_H_
diff --git a/chromium/ui/events/gesture_detection/gesture_provider_unittest.cc b/chromium/ui/events/gesture_detection/gesture_provider_unittest.cc
new file mode 100644
index 00000000000..985aa377999
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/gesture_provider_unittest.cc
@@ -0,0 +1,2298 @@
+// 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/mock_motion_event.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/gfx/geometry/point_f.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+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;
+
+ sConfig.scale_gesture_detector_config.gesture_detector_config =
+ sConfig.gesture_detector_config;
+ 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 SetMinGestureBoundsLength(float min_gesture_bound_length) {
+ GestureProvider::Config config = GetDefaultConfig();
+ config.min_gesture_bounds_length = min_gesture_bound_length;
+ 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;
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetId(++motion_event_id);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ event = ObtainMotionEvent(event_time + kOneSecond,
+ MotionEvent::ACTION_MOVE,
+ scroll_to_x,
+ scroll_to_y);
+ event.SetId(++motion_event_id);
+
+ 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(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.SetId(++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(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;
+
+ gesture_provider_->SetDoubleTapSupportForPlatformEnabled(false);
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetId(++motion_event_id);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(ET_GESTURE_TAP_DOWN, GetMostRecentGestureEventType());
+ 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.SetId(++motion_event_id);
+
+ 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(motion_event_id, GetMostRecentGestureEvent().motion_event_id);
+ 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;
+
+ gesture_provider_->SetDoubleTapSupportForPlatformEnabled(true);
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetId(++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());
+ EXPECT_EQ(BoundsForSingleMockTouchAtLocation(kFakeCoordX, kFakeCoordY),
+ GetMostRecentGestureEvent().details.bounding_box());
+
+ event = ObtainMotionEvent(event_time + kOneMicrosecond,
+ MotionEvent::ACTION_UP);
+ event.SetId(++motion_event_id);
+
+ 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(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;
+
+ MockMotionEvent event =
+ ObtainMotionEvent(event_time, MotionEvent::ACTION_DOWN);
+ event.SetId(++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());
+
+ event = ObtainMotionEvent(event_time + delta_time,
+ MotionEvent::ACTION_MOVE,
+ kFakeCoordX * 10,
+ kFakeCoordY * 10);
+ event.SetId(++motion_event_id);
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ event = ObtainMotionEvent(event_time + delta_time * 2,
+ MotionEvent::ACTION_UP,
+ kFakeCoordX * 10,
+ kFakeCoordY * 10);
+ event.SetId(++motion_event_id);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ 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_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.SetId(++motion_event_id);
+
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+
+ event = ObtainMotionEvent(event_time + delta_time,
+ MotionEvent::ACTION_MOVE,
+ kFakeCoordX * 5,
+ kFakeCoordY * 5);
+ event.SetId(++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.SetId(++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_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.SetId(++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.SetId(++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.SetId(++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.SetId(++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.SetId(++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.SetId(++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.SetId(++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.
+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());
+}
+
+// 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.SetId(++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.pointer_count = 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.pointer_count = 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.pointer_count = 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.pointer_count = 2;
+ 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(2, 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);
+ event.pointer_count = 3;
+ 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.pointer_count = 2;
+ 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(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_UP, 3, 3, 4, 4);
+ event.pointer_count = 1;
+ 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(1, 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.pointer_count = 1;
+ 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);
+ event.pointer_count = 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);
+ event.pointer_count = 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);
+ event.pointer_count = 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);
+ event.pointer_count = 3;
+ EXPECT_TRUE(gesture_provider_->OnTouchEvent(event));
+ EXPECT_EQ(7U, GetReceivedGestureCount());
+ EXPECT_EQ(3, GetReceivedGesture(4).details.touch_points());
+ EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(4).type());
+ EXPECT_EQ(2, GetReceivedGesture(5).details.touch_points());
+ EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(5).type());
+ EXPECT_EQ(1, GetReceivedGesture(6).details.touch_points());
+ EXPECT_EQ(ET_GESTURE_END, GetReceivedGesture(6).type());
+ EXPECT_EQ(1, GetReceivedGesture(4).x);
+ EXPECT_EQ(1, GetReceivedGesture(4).y);
+ EXPECT_EQ(2, GetReceivedGesture(5).x);
+ EXPECT_EQ(2, GetReceivedGesture(5).y);
+ EXPECT_EQ(3, GetReceivedGesture(6).x);
+ EXPECT_EQ(3, GetReceivedGesture(6).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;
+ SetMinGestureBoundsLength(kMinGestureBoundsLength);
+ 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());
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/mock_motion_event.cc b/chromium/ui/events/gesture_detection/mock_motion_event.cc
new file mode 100644
index 00000000000..d6e0a91b5a1
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/mock_motion_event.cc
@@ -0,0 +1,204 @@
+// 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/mock_motion_event.h"
+
+#include "base/logging.h"
+
+using base::TimeTicks;
+
+namespace ui {
+
+MockMotionEvent::MockMotionEvent()
+ : action(ACTION_CANCEL), pointer_count(1), touch_major(TOUCH_MAJOR), id(0) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action)
+ : action(action), pointer_count(1), touch_major(TOUCH_MAJOR), id(0) {
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ TimeTicks time,
+ float x,
+ float y)
+ : action(action),
+ pointer_count(1),
+ time(time),
+ touch_major(TOUCH_MAJOR),
+ id(0) {
+ points[0].SetPoint(x, y);
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ TimeTicks time,
+ float x0,
+ float y0,
+ float x1,
+ float y1)
+ : action(action),
+ pointer_count(2),
+ time(time),
+ touch_major(TOUCH_MAJOR),
+ id(0) {
+ points[0].SetPoint(x0, y0);
+ points[1].SetPoint(x1, y1);
+}
+
+MockMotionEvent::MockMotionEvent(Action action,
+ TimeTicks time,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float x2,
+ float y2)
+ : action(action),
+ pointer_count(3),
+ time(time),
+ touch_major(TOUCH_MAJOR),
+ id(0) {
+ points[0].SetPoint(x0, y0);
+ points[1].SetPoint(x1, y1);
+ points[2].SetPoint(x2, y2);
+}
+
+MockMotionEvent::MockMotionEvent(const MockMotionEvent& other)
+ : action(other.action),
+ pointer_count(other.pointer_count),
+ time(other.time),
+ touch_major(other.touch_major),
+ id(other.GetId()) {
+ for (size_t i = 0; i < pointer_count; ++i)
+ points[i] = other.points[i];
+}
+
+MockMotionEvent::~MockMotionEvent() {}
+
+MotionEvent::Action MockMotionEvent::GetAction() const { return action; }
+
+int MockMotionEvent::GetActionIndex() const {
+ return static_cast<int>(pointer_count) - 1;
+}
+
+size_t MockMotionEvent::GetPointerCount() const { return pointer_count; }
+
+int MockMotionEvent::GetId() const {
+ return id;
+}
+
+int MockMotionEvent::GetPointerId(size_t pointer_index) const {
+ DCHECK(pointer_index < pointer_count);
+ return static_cast<int>(pointer_index);
+}
+
+float MockMotionEvent::GetX(size_t pointer_index) const {
+ return points[pointer_index].x();
+}
+
+float MockMotionEvent::GetY(size_t pointer_index) const {
+ return points[pointer_index].y();
+}
+
+float MockMotionEvent::GetRawX(size_t pointer_index) const {
+ return GetX(pointer_index) + raw_offset.x();
+}
+
+float MockMotionEvent::GetRawY(size_t pointer_index) const {
+ return GetY(pointer_index) + raw_offset.y();
+}
+
+float MockMotionEvent::GetTouchMajor(size_t pointer_index) const {
+ return touch_major;
+}
+
+float MockMotionEvent::GetPressure(size_t pointer_index) const {
+ return 0;
+}
+
+TimeTicks MockMotionEvent::GetEventTime() const { return time; }
+
+size_t MockMotionEvent::GetHistorySize() const { return 0; }
+
+TimeTicks MockMotionEvent::GetHistoricalEventTime(
+ size_t historical_index) const {
+ return TimeTicks();
+}
+
+float MockMotionEvent::GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const {
+ return 0;
+}
+
+float MockMotionEvent::GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const {
+ return 0;
+}
+
+float MockMotionEvent::GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const {
+ return 0;
+}
+
+scoped_ptr<MotionEvent> MockMotionEvent::Clone() const {
+ return scoped_ptr<MotionEvent>(new MockMotionEvent(*this));
+}
+
+scoped_ptr<MotionEvent> MockMotionEvent::Cancel() const {
+ scoped_ptr<MockMotionEvent> cancel_event(new MockMotionEvent(*this));
+ cancel_event->action = MotionEvent::ACTION_CANCEL;
+ return cancel_event.PassAs<MotionEvent>();
+}
+
+void MockMotionEvent::SetId(int new_id) {
+ id = new_id;
+}
+
+void MockMotionEvent::SetTime(base::TimeTicks new_time) {
+ time = new_time;
+}
+
+void MockMotionEvent::PressPoint(float x, float y) {
+ // Reset the pointer count if the previously released and/or cancelled pointer
+ // was the last pointer in the event.
+ if (pointer_count == 1 && (action == ACTION_UP || action == ACTION_CANCEL))
+ pointer_count = 0;
+
+ DCHECK_LT(pointer_count, static_cast<size_t>(MAX_POINTERS));
+ points[pointer_count++] = gfx::PointF(x, y);
+ action = pointer_count > 1 ? ACTION_POINTER_DOWN : ACTION_DOWN;
+}
+
+void MockMotionEvent::MovePoint(size_t index, float x, float y) {
+ DCHECK_LT(index, pointer_count);
+ points[index] = gfx::PointF(x, y);
+ action = ACTION_MOVE;
+}
+
+void MockMotionEvent::ReleasePoint() {
+ DCHECK_GT(pointer_count, 0U);
+ if (pointer_count > 1) {
+ --pointer_count;
+ action = ACTION_POINTER_UP;
+ } else {
+ action = ACTION_UP;
+ }
+}
+
+void MockMotionEvent::CancelPoint() {
+ DCHECK_GT(pointer_count, 0U);
+ if (pointer_count > 1)
+ --pointer_count;
+ action = ACTION_CANCEL;
+}
+
+void MockMotionEvent::SetTouchMajor(float new_touch_major) {
+ touch_major = new_touch_major;
+}
+
+void MockMotionEvent::SetRawOffset(float raw_offset_x, float raw_offset_y) {
+ raw_offset.set_x(raw_offset_x);
+ raw_offset.set_y(raw_offset_y);
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/mock_motion_event.h b/chromium/ui/events/gesture_detection/mock_motion_event.h
new file mode 100644
index 00000000000..433de7b3b08
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/mock_motion_event.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.
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/motion_event.h"
+#include "ui/gfx/geometry/point_f.h"
+
+namespace ui {
+
+struct MockMotionEvent : public MotionEvent {
+ enum { MAX_POINTERS = 3 };
+ 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 Action GetAction() const OVERRIDE;
+ virtual int GetActionIndex() const OVERRIDE;
+ virtual size_t GetPointerCount() const OVERRIDE;
+ virtual int GetId() 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 GetPressure(size_t pointer_index) const OVERRIDE;
+ virtual base::TimeTicks GetEventTime() const OVERRIDE;
+ virtual size_t GetHistorySize() const OVERRIDE;
+ virtual base::TimeTicks GetHistoricalEventTime(size_t historical_index) const
+ OVERRIDE;
+ virtual float GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const OVERRIDE;
+ virtual float GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const OVERRIDE;
+ virtual float GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const OVERRIDE;
+
+ virtual scoped_ptr<MotionEvent> Clone() const OVERRIDE;
+ virtual scoped_ptr<MotionEvent> Cancel() const OVERRIDE;
+
+ // Utility methods.
+ void SetId(int new_id);
+ void SetTime(base::TimeTicks new_time);
+ 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);
+
+ MotionEvent::Action action;
+ size_t pointer_count;
+ gfx::PointF points[MAX_POINTERS];
+ gfx::Vector2dF raw_offset;
+ base::TimeTicks time;
+ float touch_major;
+ int id;
+};
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/motion_event.h b/chromium/ui/events/gesture_detection/motion_event.h
new file mode 100644
index 00000000000..3cf4235b78a
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/motion_event.h
@@ -0,0 +1,73 @@
+// 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,
+ };
+
+ // The implementer promises that |GetPointerId()| will never exceed this.
+ enum { MAX_POINTER_ID = 31 };
+
+ 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 GetPressure(size_t pointer_index) const = 0;
+ virtual base::TimeTicks GetEventTime() const = 0;
+
+ virtual size_t GetHistorySize() const = 0;
+ virtual base::TimeTicks GetHistoricalEventTime(
+ size_t historical_index) const = 0;
+ virtual float GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const = 0;
+ virtual float GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const = 0;
+ virtual float GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const = 0;
+
+ virtual scoped_ptr<MotionEvent> Clone() const = 0;
+ virtual scoped_ptr<MotionEvent> Cancel() const = 0;
+
+ // Utility accessor methods for convenience.
+ 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 GetPressure() const { return GetPressure(0); }
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_MOTION_EVENT_H_
diff --git a/chromium/ui/events/gesture_detection/scale_gesture_detector.cc b/chromium/ui/events/gesture_detection/scale_gesture_detector.cc
new file mode 100644
index 00000000000..18fd6d85b59
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/scale_gesture_detector.cc
@@ -0,0 +1,383 @@
+// 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"
+
+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()
+ : min_scaling_touch_major(48),
+ min_scaling_span(200),
+ quick_scale_enabled(true),
+ min_pinch_update_span_delta(0) {
+}
+
+ScaleGestureDetector::Config::~Config() {}
+
+bool ScaleGestureDetector::SimpleScaleGestureListener::OnScale(
+ const ScaleGestureDetector&, const MotionEvent&) {
+ return false;
+}
+
+bool ScaleGestureDetector::SimpleScaleGestureListener::OnScaleBegin(
+ const ScaleGestureDetector&, const MotionEvent&) {
+ return true;
+}
+
+void ScaleGestureDetector::SimpleScaleGestureListener::OnScaleEnd(
+ const ScaleGestureDetector&, const MotionEvent&) {}
+
+ScaleGestureDetector::ScaleGestureDetector(const Config& config,
+ ScaleGestureListener* listener)
+ : listener_(listener),
+ config_(config),
+ focus_x_(0),
+ focus_y_(0),
+ quick_scale_enabled_(false),
+ 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),
+ 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.gesture_detector_config.touch_slop + kSlopEpsilon) * 2;
+ touch_min_major_ = config.min_scaling_touch_major;
+ min_span_ = config.min_scaling_span + kSlopEpsilon;
+ ResetTouchHistory();
+ SetQuickScaleEnabled(config.quick_scale_enabled);
+}
+
+ScaleGestureDetector::~ScaleGestureDetector() {}
+
+bool ScaleGestureDetector::OnTouchEvent(const MotionEvent& event) {
+ curr_time_ = event.GetEventTime();
+
+ const int action = event.GetAction();
+
+ // Forward the event to check for double tap gesture.
+ if (quick_scale_enabled_) {
+ DCHECK(gesture_detector_);
+ gesture_detector_->OnTouchEvent(event);
+ }
+
+ 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_size = touch_history_last_accepted_ / 2;
+
+ const float dev_x = (dev_sum_x * inverse_unreleased_point_count) + touch_size;
+ const float dev_y = (dev_sum_y * inverse_unreleased_point_count) + touch_size;
+
+ // 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 && (InDoubleTapMode() || count > 1) &&
+ (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;
+}
+
+void ScaleGestureDetector::SetQuickScaleEnabled(bool scales) {
+ quick_scale_enabled_ = scales;
+ if (quick_scale_enabled_ && !gesture_detector_) {
+ gesture_detector_.reset(
+ new GestureDetector(config_.gesture_detector_config, this, this));
+ }
+}
+
+bool ScaleGestureDetector::IsQuickScaleEnabled() const {
+ return quick_scale_enabled_;
+}
+
+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_;
+ 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/chromium/ui/events/gesture_detection/scale_gesture_detector.h b/chromium/ui/events/gesture_detection/scale_gesture_detector.h
new file mode 100644
index 00000000000..b07f78d5785
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/scale_gesture_detector.h
@@ -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.
+
+#ifndef UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
+#define UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "ui/events/gesture_detection/gesture_detection_export.h"
+#include "ui/events/gesture_detection/gesture_detector.h"
+
+namespace ui {
+
+class MotionEvent;
+
+// 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 ScaleGestureDetector : public GestureDetector::SimpleGestureListener {
+ public:
+ struct GESTURE_DETECTION_EXPORT Config {
+ Config();
+ ~Config();
+ GestureDetector::Config gesture_detector_config;
+
+ // 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;
+
+ // Whether double-tap drag scaling is enabled.
+ bool quick_scale_enabled;
+
+ // Minimum pinch span change before pinch occurs (in dips). See
+ // crbug.com/373318.
+ float min_pinch_update_span_delta;
+ };
+
+ class 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 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;
+ };
+
+ 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);
+
+ // Set whether the associated |ScaleGestureListener| should receive
+ // OnScale callbacks when the user performs a doubletap followed by a swipe.
+ void SetQuickScaleEnabled(bool scales);
+ bool IsQuickScaleEnabled() const;
+ 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 };
+
+ // DoubleTapListener implementation.
+ virtual bool OnDoubleTap(const MotionEvent& ev) OVERRIDE;
+
+ // 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_;
+
+ Config config_;
+
+ float focus_x_;
+ float focus_y_;
+
+ bool quick_scale_enabled_;
+
+ 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 double_tap_focus_x_;
+ float double_tap_focus_y_;
+ DoubleTapMode double_tap_mode_;
+
+ bool event_before_or_above_starting_gesture_event_;
+
+ scoped_ptr<GestureDetector> gesture_detector_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScaleGestureDetector);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_SCALE_GESTURE_DETECTOR_H_
diff --git a/chromium/ui/events/gesture_detection/snap_scroll_controller.cc b/chromium/ui/events/gesture_detection/snap_scroll_controller.cc
new file mode 100644
index 00000000000..bde5a35602d
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/snap_scroll_controller.h b/chromium/ui/events/gesture_detection/snap_scroll_controller.h
new file mode 100644
index 00000000000..753c2bcb201
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
new file mode 100644
index 00000000000..0e775dd9c19
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.cc
@@ -0,0 +1,400 @@
+// 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,
+ const GestureEventDataPacket& packet) {
+ return GestureEventData(GestureEventDetails(type, 0, 0),
+ motion_event_id,
+ 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()));
+}
+
+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 | RT_CURRENT);
+ 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),
+ 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);
+ }
+
+ 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);
+ return;
+ }
+
+ 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);
+ }
+}
+
+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;
+ 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;
+ 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;
+ 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_,
+ 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_,
+ 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_,
+ 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/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h
new file mode 100644
index 00000000000..6e57bdb5235
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter.h
@@ -0,0 +1,108 @@
+// 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_;
+ 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/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
new file mode 100644
index 00000000000..a5471f73cff
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/touch_disposition_gesture_filter_unittest.cc
@@ -0,0 +1,1059 @@
+// 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/mock_motion_event.h"
+#include "ui/events/gesture_detection/touch_disposition_gesture_filter.h"
+
+namespace ui {
+
+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));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ queue_.reset();
+ }
+
+ // TouchDispositionGestureFilterClient
+ virtual void ForwardGestureEvent(const GestureEventData& event) OVERRIDE {
+ ++sent_gesture_count_;
+ last_sent_gesture_time_ = event.time;
+ sent_gestures_.push_back(event.type());
+ last_sent_gesture_location_ = gfx::PointF(event.x, event.y);
+ last_sent_gesture_raw_location_ = gfx::PointF(event.raw_x, event.raw_y);
+ if (cancel_after_next_gesture_) {
+ CancelTouchPoint();
+ SendTouchNotConsumedAck();
+ cancel_after_next_gesture_ = false;
+ }
+ }
+
+ 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_.SetTime(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 PressTouchPoint(int x, int y) {
+ touch_event_.PressPoint(x, y);
+ SendTouchGestures();
+ }
+
+ void MoveTouchPoint(size_t index, int x, int y) {
+ touch_event_.MovePoint(index, x, y);
+ SendTouchGestures();
+ }
+
+ void ReleaseTouchPoint() {
+ touch_event_.ReleasePoint();
+ SendTouchGestures();
+ }
+
+ void CancelTouchPoint() {
+ touch_event_.CancelPoint();
+ SendTouchGestures();
+ }
+
+ void SetRawTouchOffset(const gfx::Vector2dF& raw_offset) {
+ touch_event_.SetRawOffset(raw_offset.x(), raw_offset.y());
+ }
+
+ void ResetTouchPoints() { touch_event_ = MockMotionEvent(); }
+
+ bool GesturesSent() const { return !sent_gestures_.empty(); }
+
+ base::TimeTicks LastSentGestureTime() const {
+ 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;
+ }
+
+ const gfx::PointF& LastSentGestureLocation() const {
+ return last_sent_gesture_location_;
+ }
+
+ const gfx::PointF& LastSentGestureRawLocation() const {
+ return last_sent_gesture_raw_location_;
+ }
+
+ void SetCancelAfterNextGesture(bool cancel_after_next_gesture) {
+ cancel_after_next_gesture_ = cancel_after_next_gesture;
+ }
+
+ GestureEventData CreateGesture(EventType type) {
+ return GestureEventData(GestureEventDetails(type, 0, 0),
+ 0,
+ base::TimeTicks(),
+ touch_event_.GetX(0),
+ touch_event_.GetY(0),
+ touch_event_.GetRawX(0),
+ touch_event_.GetRawY(0),
+ 1,
+ gfx::RectF(0, 0, 0, 0));
+ }
+
+ private:
+ scoped_ptr<TouchDispositionGestureFilter> queue_;
+ bool cancel_after_next_gesture_;
+ MockMotionEvent touch_event_;
+ GestureEventDataPacket pending_gesture_packet_;
+ size_t sent_gesture_count_;
+ base::TimeTicks last_sent_gesture_time_;
+ GestureList sent_gestures_;
+ gfx::PointF last_sent_gesture_location_;
+ gfx::PointF last_sent_gesture_raw_location_;
+};
+
+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),
+ GetAndResetSentGestures()));
+ EXPECT_EQ(LastSentGestureLocation(), gfx::PointF(2, 2));
+}
+
+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_END, ET_GESTURE_SCROLL_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_FALSE(GesturesSent());
+
+ PushGesture(ET_GESTURE_PINCH_BEGIN);
+ PressTouchPoint(1, 1);
+ SendTouchNotConsumedAck();
+ EXPECT_FALSE(GesturesSent());
+
+ PushGesture(ET_GESTURE_SWIPE);
+ PressTouchPoint(1, 1);
+ SendTouchNotConsumedAck();
+ 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()));
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_detection/velocity_tracker.cc b/chromium/ui/events/gesture_detection/velocity_tracker.cc
new file mode 100644
index 00000000000..da9f8a7bf15
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/velocity_tracker.cc
@@ -0,0 +1,805 @@
+// 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 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 kAssumePointerStoppedTimeMs = 40;
+
+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(
+ kAssumePointerStoppedTimeMs))) {
+ // 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;
+ 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.
+ // 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.
+ 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/chromium/ui/events/gesture_detection/velocity_tracker.h b/chromium/ui/events/gesture_detection/velocity_tracker.h
new file mode 100644
index 00000000000..8147695dd25
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/velocity_tracker_state.cc b/chromium/ui/events/gesture_detection/velocity_tracker_state.cc
new file mode 100644
index 00000000000..12168560dbd
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/velocity_tracker_state.h b/chromium/ui/events/gesture_detection/velocity_tracker_state.h
new file mode 100644
index 00000000000..780871cb80b
--- /dev/null
+++ b/chromium/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/chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc b/chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc
new file mode 100644
index 00000000000..eda1606d141
--- /dev/null
+++ b/chromium/ui/events/gesture_detection/velocity_tracker_unittest.cc
@@ -0,0 +1,193 @@
+// 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/mock_motion_event.h"
+#include "ui/events/gesture_detection/velocity_tracker_state.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/vector2d_f.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+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);
+ }
+}
+
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_event_details.cc b/chromium/ui/events/gesture_event_details.cc
new file mode 100644
index 00000000000..4026d06ecdb
--- /dev/null
+++ b/chromium/ui/events/gesture_event_details.cc
@@ -0,0 +1,71 @@
+// 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) {}
+
+GestureEventDetails::GestureEventDetails(ui::EventType type,
+ float delta_x,
+ float delta_y)
+ : type_(type),
+ touch_points_(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_PINCH_UPDATE:
+ data.scale = delta_x;
+ CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for pinch";
+ 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;
+
+ case ui::ET_GESTURE_TAP:
+ case ui::ET_GESTURE_DOUBLE_TAP:
+ case ui::ET_GESTURE_TAP_UNCONFIRMED:
+ data.tap_count = static_cast<int>(delta_x);
+ CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for tap.";
+ break;
+
+ default:
+ if (delta_x != 0.f || delta_y != 0.f) {
+ DLOG(WARNING) << "A gesture event (" << type << ") had unknown data: ("
+ << delta_x << "," << delta_y;
+ }
+ break;
+ }
+}
+
+GestureEventDetails::Details::Details() {
+ memset(this, 0, sizeof(Details));
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gesture_event_details.h b/chromium/ui/events/gesture_event_details.h
new file mode 100644
index 00000000000..32db808bf72
--- /dev/null
+++ b/chromium/ui/events/gesture_event_details.h
@@ -0,0 +1,171 @@
+// 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();
+ 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;
+ }
+
+ // 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;
+ }
+
+ 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_;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_GESTURE_EVENT_DETAILS_H_
diff --git a/chromium/ui/events/gestures/OWNERS b/chromium/ui/events/gestures/OWNERS
index 6a1dd965022..92704b0eb58 100644
--- a/chromium/ui/events/gestures/OWNERS
+++ b/chromium/ui/events/gestures/OWNERS
@@ -1,2 +1,3 @@
rjkroege@chromium.org
sadrul@chromium.org
+tdresser@chromium.org
diff --git a/chromium/ui/events/gestures/gesture_configuration.cc b/chromium/ui/events/gestures/gesture_configuration.cc
index e48ae6daed3..8623e9973ee 100644
--- a/chromium/ui/events/gestures/gesture_configuration.cc
+++ b/chromium/ui/events/gestures/gesture_configuration.cc
@@ -6,7 +6,7 @@
namespace ui {
-int GestureConfiguration::default_radius_ = 15;
+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;
@@ -21,20 +21,23 @@ double
double GestureConfiguration::max_swipe_deviation_ratio_ = 3;
double
GestureConfiguration::max_touch_down_duration_in_seconds_for_click_ = 0.8;
-double GestureConfiguration::max_touch_move_in_pixels_for_click_ = 25;
+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_flick_speed_squared_ = 550.f * 550.f;
double GestureConfiguration::min_pinch_update_distance_in_pixels_ = 5;
double GestureConfiguration::min_rail_break_velocity_ = 200;
double GestureConfiguration::min_scroll_delta_squared_ = 4 * 4;
-int GestureConfiguration::min_scroll_successive_velocity_events_ = 4;
float GestureConfiguration::min_scroll_velocity_ = 30.0f;
double GestureConfiguration::min_swipe_speed_ = 20;
double GestureConfiguration::scroll_prediction_seconds_ = 0.03;
double
GestureConfiguration::min_touch_down_duration_in_seconds_for_click_ = 0.01;
+// 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;
+
// The number of points used in the linear regression which determines
// touch velocity. Velocity is reported for 2 or more touch move events.
int GestureConfiguration::points_buffered_for_velocity_ = 8;
@@ -42,6 +45,14 @@ double GestureConfiguration::rail_break_proportion_ = 15;
double GestureConfiguration::rail_start_proportion_ = 2;
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
+
// Coefficients for a function that computes fling acceleration.
// These are empirically determined defaults. Do not adjust without
// additional empirical validation.
diff --git a/chromium/ui/events/gestures/gesture_configuration.h b/chromium/ui/events/gestures/gesture_configuration.h
index a4bdcd3f4c2..08ce578eeeb 100644
--- a/chromium/ui/events/gestures/gesture_configuration.h
+++ b/chromium/ui/events/gestures/gesture_configuration.h
@@ -6,7 +6,7 @@
#define UI_EVENTS_GESTURES_GESTURE_CONFIGURATION_H_
#include "base/basictypes.h"
-#include "ui/events/events_export.h"
+#include "ui/events/events_base_export.h"
namespace ui {
@@ -14,7 +14,7 @@ namespace ui {
// approaches (windows, chrome, others). This would turn into an
// abstract base class.
-class EVENTS_EXPORT GestureConfiguration {
+class EVENTS_BASE_EXPORT GestureConfiguration {
public:
// Number of parameters in the array of parameters for the fling acceleration
// curve.
@@ -125,12 +125,6 @@ class EVENTS_EXPORT GestureConfiguration {
static void set_min_scroll_delta_squared(double val) {
min_scroll_delta_squared_ = val;
}
- static int min_scroll_successive_velocity_events() {
- return min_scroll_successive_velocity_events_;
- }
- static void set_min_scroll_successive_velocity_events(int val) {
- min_scroll_successive_velocity_events_ = val;
- }
static float min_scroll_velocity() {
return min_scroll_velocity_;
}
@@ -149,6 +143,15 @@ class EVENTS_EXPORT GestureConfiguration {
static void set_min_touch_down_duration_in_seconds_for_click(double val) {
min_touch_down_duration_in_seconds_for_click_ = 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 points_buffered_for_velocity() {
return points_buffered_for_velocity_;
}
@@ -179,6 +182,12 @@ class EVENTS_EXPORT GestureConfiguration {
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 void set_fling_acceleration_curve_coefficients(int i, float val) {
fling_acceleration_curve_coefficients_[i] = val;
}
@@ -238,15 +247,17 @@ class EVENTS_EXPORT GestureConfiguration {
static double min_pinch_update_distance_in_pixels_;
static double min_rail_break_velocity_;
static double min_scroll_delta_squared_;
- static int min_scroll_successive_velocity_events_;
static float min_scroll_velocity_;
static double min_swipe_speed_;
static double min_touch_down_duration_in_seconds_for_click_;
+ static int min_scaling_span_in_pixels_;
static int points_buffered_for_velocity_;
static double rail_break_proportion_;
static double rail_start_proportion_;
static double scroll_prediction_seconds_;
static int show_press_delay_in_ms_;
+ static int scroll_debounce_interval_in_ms_;
+
static float fling_acceleration_curve_coefficients_[NumAccelParams];
static float fling_velocity_cap_;
// TODO(davemoore): Move into chrome/browser/ui.
diff --git a/chromium/ui/events/gestures/gesture_point.cc b/chromium/ui/events/gestures/gesture_point.cc
index 529471af8b4..33966235a10 100644
--- a/chromium/ui/events/gestures/gesture_point.cc
+++ b/chromium/ui/events/gestures/gesture_point.cc
@@ -11,7 +11,6 @@
#include "ui/events/event_constants.h"
#include "ui/events/gestures/gesture_configuration.h"
#include "ui/events/gestures/gesture_types.h"
-#include "ui/events/gestures/gesture_util.h"
namespace ui {
@@ -24,7 +23,14 @@ GesturePoint::GesturePoint()
velocity_calculator_(
GestureConfiguration::points_buffered_for_velocity()),
point_id_(-1),
- touch_id_(-1) {
+ touch_id_(-1),
+ source_device_id_(-1) {
+ max_touch_move_in_pixels_for_click_squared_ =
+ GestureConfiguration::max_touch_move_in_pixels_for_click() *
+ GestureConfiguration::max_touch_move_in_pixels_for_click();
+ max_distance_between_taps_for_double_tap_squared_ =
+ GestureConfiguration::max_distance_between_taps_for_double_tap() *
+ GestureConfiguration::max_distance_between_taps_for_double_tap();
}
GesturePoint::~GesturePoint() {}
@@ -34,14 +40,14 @@ void GesturePoint::Reset() {
ResetVelocity();
point_id_ = -1;
clear_enclosing_rectangle();
+ source_device_id_ = -1;
}
void GesturePoint::ResetVelocity() {
velocity_calculator_.ClearHistory();
- same_direction_count_ = gfx::Vector2d();
}
-gfx::Vector2d GesturePoint::ScrollDelta() {
+gfx::Vector2dF GesturePoint::ScrollDelta() const {
return last_touch_position_ - second_last_touch_position_;
}
@@ -54,11 +60,10 @@ void GesturePoint::UpdateValues(const TouchEvent& event) {
event_timestamp_microseconds);
gfx::Vector2d sd(ScrollVelocityDirection(velocity_calculator_.XVelocity()),
ScrollVelocityDirection(velocity_calculator_.YVelocity()));
- same_direction_count_ = same_direction_count_ + sd;
}
last_touch_time_ = event.time_stamp().InSecondsF();
- last_touch_position_ = event.location();
+ last_touch_position_ = event.location_f();
if (event.type() == ui::ET_TOUCH_PRESSED) {
ResetVelocity();
@@ -86,31 +91,30 @@ void GesturePoint::UpdateForTap() {
void GesturePoint::UpdateForScroll() {
second_last_touch_position_ = last_touch_position_;
second_last_touch_time_ = last_touch_time_;
- same_direction_count_ = gfx::Vector2d();
}
bool GesturePoint::IsInClickWindow(const TouchEvent& event) const {
- return IsInClickTimeWindow() && IsInsideManhattanSquare(event);
+ return IsInClickTimeWindow() && IsInsideTouchSlopRegion(event);
}
bool GesturePoint::IsInDoubleClickWindow(const TouchEvent& event) const {
return IsInClickAggregateTimeWindow(last_tap_time_, last_touch_time_) &&
- IsPointInsideManhattanSquare(event.location(), last_tap_position_);
+ IsPointInsideDoubleTapTouchSlopRegion(
+ event.location(), last_tap_position_);
}
bool GesturePoint::IsInTripleClickWindow(const TouchEvent& event) const {
return IsInClickAggregateTimeWindow(last_tap_time_, last_touch_time_) &&
IsInClickAggregateTimeWindow(second_last_tap_time_, last_tap_time_) &&
- IsPointInsideManhattanSquare(event.location(), last_tap_position_) &&
- IsPointInsideManhattanSquare(last_tap_position_,
+ IsPointInsideDoubleTapTouchSlopRegion(
+ event.location(), last_tap_position_) &&
+ IsPointInsideDoubleTapTouchSlopRegion(last_tap_position_,
second_last_tap_position_);
}
bool GesturePoint::IsInScrollWindow(const TouchEvent& event) const {
- if (IsConsistentScrollingActionUnderway())
- return true;
return event.type() == ui::ET_TOUCH_MOVED &&
- !IsInsideManhattanSquare(event);
+ !IsInsideTouchSlopRegion(event);
}
bool GesturePoint::IsInFlickWindow(const TouchEvent& event) {
@@ -128,28 +132,20 @@ int GesturePoint::ScrollVelocityDirection(float v) {
}
bool GesturePoint::DidScroll(const TouchEvent& event, int dist) const {
- gfx::Vector2d d = last_touch_position_ - second_last_touch_position_;
- return abs(d.x()) > dist || abs(d.y()) > dist;
-}
-
-bool GesturePoint::IsConsistentScrollingActionUnderway() const {
- int me = GestureConfiguration::min_scroll_successive_velocity_events();
- if (abs(same_direction_count_.x()) >= me ||
- abs(same_direction_count_.y()) >= me)
- return true;
- return false;
+ gfx::Vector2dF d = last_touch_position_ - second_last_touch_position_;
+ return fabs(d.x()) > dist || fabs(d.y()) > dist;
}
bool GesturePoint::IsInHorizontalRailWindow() const {
- gfx::Vector2d d = last_touch_position_ - second_last_touch_position_;
- return abs(d.x()) >
- GestureConfiguration::rail_start_proportion() * abs(d.y());
+ gfx::Vector2dF d = last_touch_position_ - second_last_touch_position_;
+ return std::abs(d.x()) >
+ GestureConfiguration::rail_start_proportion() * std::abs(d.y());
}
bool GesturePoint::IsInVerticalRailWindow() const {
- gfx::Vector2d d = last_touch_position_ - second_last_touch_position_;
- return abs(d.y()) >
- GestureConfiguration::rail_start_proportion() * abs(d.x());
+ gfx::Vector2dF d = last_touch_position_ - second_last_touch_position_;
+ return std::abs(d.y()) >
+ GestureConfiguration::rail_start_proportion() * std::abs(d.x());
}
bool GesturePoint::BreaksHorizontalRail() {
@@ -180,16 +176,21 @@ bool GesturePoint::IsInClickAggregateTimeWindow(double before,
return duration < GestureConfiguration::max_seconds_between_double_click();
}
-bool GesturePoint::IsInsideManhattanSquare(const TouchEvent& event) const {
- return ui::gestures::IsInsideManhattanSquare(event.location(),
- first_touch_position_);
+bool GesturePoint::IsInsideTouchSlopRegion(const TouchEvent& event) const {
+ const gfx::PointF& p1 = event.location();
+ const gfx::PointF& p2 = first_touch_position_;
+ float dx = p1.x() - p2.x();
+ float dy = p1.y() - p2.y();
+ float distance = dx * dx + dy * dy;
+ return distance < max_touch_move_in_pixels_for_click_squared_;
}
-bool GesturePoint::IsPointInsideManhattanSquare(gfx::Point p1,
- gfx::Point p2) const {
- int manhattan_distance = abs(p1.x() - p2.x()) + abs(p1.y() - p2.y());
- return manhattan_distance <
- GestureConfiguration::max_distance_between_taps_for_double_tap();
+bool GesturePoint::IsPointInsideDoubleTapTouchSlopRegion(gfx::PointF p1,
+ gfx::PointF p2) const {
+ float dx = p1.x() - p2.x();
+ float dy = p1.y() - p2.y();
+ float distance = dx * dx + dy * dy;
+ return distance < max_distance_between_taps_for_double_tap_squared_;
}
bool GesturePoint::IsOverMinFlickSpeed() {
@@ -217,10 +218,10 @@ void GesturePoint::UpdateEnclosingRectangle(const TouchEvent& event) {
else
radius = GestureConfiguration::default_radius();
- gfx::Rect rect(event.location().x() - radius,
- event.location().y() - radius,
- radius * 2,
- radius * 2);
+ gfx::RectF rect(event.location_f().x() - radius,
+ event.location_f().y() - radius,
+ radius * 2,
+ radius * 2);
if (IsInClickWindow(event))
enclosing_rect_.Union(rect);
else
diff --git a/chromium/ui/events/gestures/gesture_point.h b/chromium/ui/events/gestures/gesture_point.h
index 02aa4608592..f20cf9dc7ec 100644
--- a/chromium/ui/events/gestures/gesture_point.h
+++ b/chromium/ui/events/gestures/gesture_point.h
@@ -43,20 +43,22 @@ class GesturePoint {
bool IsInFlickWindow(const TouchEvent& event);
bool IsInHorizontalRailWindow() const;
bool IsInVerticalRailWindow() const;
- bool IsInsideManhattanSquare(const TouchEvent& event) const;
+ bool IsInsideTouchSlopRegion(const TouchEvent& event) const;
bool IsInScrollWindow(const TouchEvent& event) const;
bool BreaksHorizontalRail();
bool BreaksVerticalRail();
bool DidScroll(const TouchEvent& event, int distance) const;
- const gfx::Point& first_touch_position() const {
+ const gfx::PointF& first_touch_position() const {
return first_touch_position_;
}
double last_touch_time() const { return last_touch_time_; }
- const gfx::Point& last_touch_position() const { return last_touch_position_; }
- int x() const { return last_touch_position_.x(); }
- int y() const { return last_touch_position_.y(); }
+ const gfx::PointF& last_touch_position() const {
+ return last_touch_position_;
+ }
+ float x() const { return last_touch_position_.x(); }
+ float y() const { return last_touch_position_.y(); }
// point_id_ is used to drive GestureSequence::ProcessTouchEventForGesture.
// point_ids are maintained such that the set of point_ids is always
@@ -72,22 +74,25 @@ class GesturePoint {
bool in_use() const { return point_id_ >= 0; }
- gfx::Vector2d ScrollDelta();
+ gfx::Vector2dF ScrollDelta() const;
float XVelocity() { return velocity_calculator_.XVelocity(); }
float YVelocity() { return velocity_calculator_.YVelocity(); }
- const gfx::Rect& enclosing_rectangle() const { return enclosing_rect_; }
+ const gfx::RectF& enclosing_rectangle() const { return enclosing_rect_; }
+
+ void set_source_device_id(int source_device_id) {
+ source_device_id_ = source_device_id;
+ }
+ int source_device_id() const { return source_device_id_; }
private:
// Various statistical functions to manipulate gestures.
- // Tests if the point has a consistent scroll vector across a window of touch
- // move events.
- bool IsConsistentScrollingActionUnderway() const;
bool IsInClickTimeWindow() const;
bool IsInClickAggregateTimeWindow(double before, double after) const;
- bool IsPointInsideManhattanSquare(gfx::Point p1, gfx::Point p2) const;
+ bool IsPointInsideDoubleTapTouchSlopRegion(
+ gfx::PointF p1, gfx::PointF p2) const;
bool IsOverMinFlickSpeed();
// Returns -1, 0, 1 for |v| below the negative velocity threshold,
@@ -101,23 +106,23 @@ class GesturePoint {
// cleared on a ET_TOUCH_PRESSED event (i.e., at the beginning of a possible
// GESTURE_TAP event) or when Reset is called.
void UpdateEnclosingRectangle(const TouchEvent& event);
- void clear_enclosing_rectangle() { enclosing_rect_ = gfx::Rect(); }
+ void clear_enclosing_rectangle() { enclosing_rect_ = gfx::RectF(); }
// The position of the first touchdown event.
- gfx::Point first_touch_position_;
+ gfx::PointF first_touch_position_;
double first_touch_time_;
- gfx::Point second_last_touch_position_;
+ gfx::PointF second_last_touch_position_;
double second_last_touch_time_;
- gfx::Point last_touch_position_;
+ gfx::PointF last_touch_position_;
double last_touch_time_;
double second_last_tap_time_;
- gfx::Point second_last_tap_position_;
+ gfx::PointF second_last_tap_position_;
double last_tap_time_;
- gfx::Point last_tap_position_;
+ gfx::PointF last_tap_position_;
VelocityCalculator velocity_calculator_;
@@ -126,10 +131,12 @@ class GesturePoint {
// Represents the rectangle that encloses the circles/ellipses
// generated by a sequence of touch events
- gfx::Rect enclosing_rect_;
+ gfx::RectF enclosing_rect_;
+
+ int source_device_id_;
- // Count of the number of events with same direction.
- gfx::Vector2d same_direction_count_;
+ float max_touch_move_in_pixels_for_click_squared_;
+ float max_distance_between_taps_for_double_tap_squared_;
DISALLOW_COPY_AND_ASSIGN(GesturePoint);
};
diff --git a/chromium/ui/events/gestures/gesture_provider_aura.cc b/chromium/ui/events/gestures/gesture_provider_aura.cc
new file mode 100644
index 00000000000..eb902f507f8
--- /dev/null
+++ b/chromium/ui/events/gestures/gesture_provider_aura.cc
@@ -0,0 +1,142 @@
+// 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) {
+ bool pointer_id_is_active = false;
+ for (size_t i = 0; i < pointer_state_.GetPointerCount(); ++i) {
+ if (event.touch_id() != pointer_state_.GetPointerId(i))
+ continue;
+ pointer_id_is_active = true;
+ break;
+ }
+
+ 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;
+ }
+
+ last_touch_event_flags_ = event.flags();
+ 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;
+
+ 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.type(),
+ gesture.x,
+ gesture.y,
+ last_touch_event_flags_,
+ gesture.time - base::TimeTicks(),
+ details,
+ // ui::GestureEvent stores a bitfield indicating the
+ // ids of active touch points. This is currently only
+ // used when one finger is down, and will eventually
+ // be cleaned up. See crbug.com/366707.
+ 1 << gesture.motion_event_id));
+
+
+ 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/chromium/ui/events/gestures/gesture_provider_aura.h b/chromium/ui/events/gestures/gesture_provider_aura.h
new file mode 100644
index 00000000000..ff7befce15e
--- /dev/null
+++ b/chromium/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 "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_;
+
+ int last_touch_event_flags_;
+ 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/chromium/ui/events/gestures/gesture_recognizer.h b/chromium/ui/events/gestures/gesture_recognizer.h
index 92d225a7118..4b31e57bd71 100644
--- a/chromium/ui/events/gestures/gesture_recognizer.h
+++ b/chromium/ui/events/gestures/gesture_recognizer.h
@@ -11,6 +11,7 @@
#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
@@ -19,6 +20,7 @@ class EVENTS_EXPORT GestureRecognizer {
public:
static GestureRecognizer* Create();
static GestureRecognizer* Get();
+ static void Reset();
// List of GestureEvent*.
typedef ScopedVector<GestureEvent> Gestures;
@@ -34,8 +36,9 @@ class EVENTS_EXPORT GestureRecognizer {
GestureConsumer* consumer) = 0;
// This is called when the consumer is destroyed. So this should cleanup any
- // internal state maintained for |consumer|.
- virtual void CleanupStateForConsumer(GestureConsumer* consumer) = 0;
+ // 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.
@@ -46,10 +49,12 @@ class EVENTS_EXPORT GestureRecognizer {
virtual GestureConsumer* GetTargetForGestureEvent(
const GestureEvent& event) = 0;
- // If there is an active touch within
- // GestureConfiguration::max_separation_for_gesture_touches_in_pixels,
- // of |location|, returns the target of the nearest active touch.
- virtual GestureConsumer* GetTargetForLocation(const gfx::Point& location) = 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.
@@ -65,10 +70,11 @@ class EVENTS_EXPORT GestureRecognizer {
// 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::Point* point) = 0;
+ gfx::PointF* point) = 0;
- // Sends a touch cancel event for every active touch.
- virtual void CancelActiveTouches(GestureConsumer* consumer) = 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
diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl.cc b/chromium/ui/events/gestures/gesture_recognizer_impl.cc
index 4a599345c84..e145334454b 100644
--- a/chromium/ui/events/gestures/gesture_recognizer_impl.cc
+++ b/chromium/ui/events/gestures/gesture_recognizer_impl.cc
@@ -4,15 +4,21 @@
#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_sequence.h"
#include "ui/events/gestures/gesture_types.h"
+#include "ui/events/gestures/unified_gesture_detector_enabled.h"
namespace ui {
@@ -28,15 +34,19 @@ void TransferConsumer(GestureConsumer* current_consumer,
}
}
-void RemoveConsumerFromMap(GestureConsumer* 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)
+ if (i->second == consumer) {
map->erase(i++);
- else
+ consumer_removed = true;
+ } else {
++i;
+ }
}
+ return consumer_removed;
}
void TransferTouchIdToConsumerMap(
@@ -50,16 +60,22 @@ void TransferTouchIdToConsumerMap(
}
}
+GestureProviderAura* CreateGestureProvider(GestureProviderAuraClient* client) {
+ return new GestureProviderAura(client);
+}
+
} // namespace
////////////////////////////////////////////////////////////////////////////////
// GestureRecognizerImpl, public:
GestureRecognizerImpl::GestureRecognizerImpl() {
+ use_unified_gesture_detector_ = IsUnifiedGestureDetectorEnabled();
}
GestureRecognizerImpl::~GestureRecognizerImpl() {
STLDeleteValues(&consumer_sequence_);
+ STLDeleteValues(&consumer_gesture_provider_);
}
// Checks if this finger is already down, if so, returns the current target.
@@ -78,33 +94,66 @@ GestureConsumer* GestureRecognizerImpl::GetTargetForGestureEvent(
}
GestureConsumer* GestureRecognizerImpl::GetTargetForLocation(
- const gfx::Point& location) {
- const GesturePoint* closest_point = NULL;
- int64 closest_distance_squared = 0;
- std::map<GestureConsumer*, GestureSequence*>::iterator i;
- for (i = consumer_sequence_.begin(); i != consumer_sequence_.end(); ++i) {
- const GesturePoint* points = i->second->points();
- for (int j = 0; j < GestureSequence::kMaxGesturePoints; ++j) {
- if (!points[j].in_use())
- continue;
- gfx::Vector2d delta = points[j].last_touch_position() - location;
- // Relative distance is all we need here, so LengthSquared() is
- // appropriate, and cheaper than Length().
- int64 distance_squared = delta.LengthSquared();
- if (!closest_point || distance_squared < closest_distance_squared) {
- closest_point = &points[j];
- closest_distance_squared = distance_squared;
+ const gfx::PointF& location, int source_device_id) {
+ const int max_distance =
+ GestureConfiguration::max_separation_for_gesture_touches_in_pixels();
+
+ if (!use_unified_gesture_detector_) {
+ const GesturePoint* closest_point = NULL;
+ int64 closest_distance_squared = 0;
+ std::map<GestureConsumer*, GestureSequence*>::iterator i;
+ for (i = consumer_sequence_.begin(); i != consumer_sequence_.end(); ++i) {
+ const GesturePoint* points = i->second->points();
+ for (int j = 0; j < GestureSequence::kMaxGesturePoints; ++j) {
+ if (!points[j].in_use() ||
+ source_device_id != points[j].source_device_id()) {
+ continue;
+ }
+ gfx::Vector2dF delta = points[j].last_touch_position() - location;
+ // Relative distance is all we need here, so LengthSquared() is
+ // appropriate, and cheaper than Length().
+ int64 distance_squared = delta.LengthSquared();
+ if (!closest_point || distance_squared < closest_distance_squared) {
+ closest_point = &points[j];
+ closest_distance_squared = distance_squared;
+ }
}
}
- }
- const int max_distance =
- GestureConfiguration::max_separation_for_gesture_touches_in_pixels();
+ if (closest_distance_squared < max_distance * max_distance && closest_point)
+ return touch_id_target_[closest_point->touch_id()];
+ else
+ return NULL;
+ } else {
+ 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 && closest_point)
- return touch_id_target_[closest_point->touch_id()];
- else
- return NULL;
+ if (closest_distance_squared < max_distance * max_distance)
+ return touch_id_target_[closest_touch_id];
+ else
+ return NULL;
+ }
}
void GestureRecognizerImpl::TransferEventsTo(GestureConsumer* current_consumer,
@@ -134,29 +183,42 @@ void GestureRecognizerImpl::TransferEventsTo(GestureConsumer* current_consumer,
&touch_id_target_);
TransferTouchIdToConsumerMap(current_consumer, new_consumer,
&touch_id_target_for_gestures_);
- TransferConsumer(current_consumer, new_consumer, &consumer_sequence_);
+ if (!use_unified_gesture_detector_)
+ TransferConsumer(current_consumer, new_consumer, &consumer_sequence_);
+ else
+ TransferConsumer(
+ current_consumer, new_consumer, &consumer_gesture_provider_);
}
}
bool GestureRecognizerImpl::GetLastTouchPointForTarget(
GestureConsumer* consumer,
- gfx::Point* point) {
- if (consumer_sequence_.count(consumer) == 0)
- return false;
-
- *point = consumer_sequence_[consumer]->last_touch_location();
- return true;
+ gfx::PointF* point) {
+ if (!use_unified_gesture_detector_) {
+ if (consumer_sequence_.count(consumer) == 0)
+ return false;
+ *point = consumer_sequence_[consumer]->last_touch_location();
+ return true;
+ } else {
+ 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;
+ }
}
-void GestureRecognizerImpl::CancelActiveTouches(
- GestureConsumer* consumer) {
+bool GestureRecognizerImpl::CancelActiveTouches(GestureConsumer* consumer) {
std::vector<std::pair<int, GestureConsumer*> > ids;
for (TouchIdToConsumerMap::const_iterator i = touch_id_target_.begin();
i != touch_id_target_.end(); ++i) {
if (i->second == consumer)
ids.push_back(std::make_pair(i->first, i->second));
}
+ bool cancelled_touch = !ids.empty();
CancelTouches(&ids);
+ return cancelled_touch;
}
////////////////////////////////////////////////////////////////////////////////
@@ -180,6 +242,16 @@ GestureSequence* GestureRecognizerImpl::GetGestureSequenceForConsumer(
return gesture_sequence;
}
+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 ||
@@ -197,7 +269,7 @@ void GestureRecognizerImpl::CancelTouches(
while (!touches->empty()) {
int touch_id = touches->begin()->first;
GestureConsumer* target = touches->begin()->second;
- TouchEvent touch_event(ui::ET_TOUCH_CANCELLED, gfx::Point(0, 0),
+ TouchEvent touch_event(ui::ET_TOUCH_CANCELLED, gfx::PointF(0, 0),
ui::EF_IS_SYNTHESIZED, touch_id,
ui::EventTimeForNow(), 0.0f, 0.0f, 0.0f, 0.0f);
GestureEventHelper* helper = FindDispatchHelperForConsumer(target);
@@ -207,23 +279,60 @@ void GestureRecognizerImpl::CancelTouches(
}
}
-GestureSequence::Gestures* GestureRecognizerImpl::ProcessTouchEventForGesture(
+void GestureRecognizerImpl::DispatchGestureEvent(GestureEvent* event) {
+ GestureConsumer* consumer = GetTargetForGestureEvent(*event);
+ if (consumer) {
+ GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
+ if (helper)
+ helper->DispatchGestureEvent(event);
+ }
+}
+
+ScopedVector<GestureEvent>* GestureRecognizerImpl::ProcessTouchEventForGesture(
const TouchEvent& event,
ui::EventResult result,
GestureConsumer* target) {
SetupTargets(event, target);
- GestureSequence* gesture_sequence = GetGestureSequenceForConsumer(target);
- return gesture_sequence->ProcessTouchEventForGesture(event, result);
+
+ if (!use_unified_gesture_detector_) {
+ GestureSequence* gesture_sequence = GetGestureSequenceForConsumer(target);
+ return gesture_sequence->ProcessTouchEventForGesture(event, result);
+ } else {
+ GestureProviderAura* gesture_provider =
+ GetGestureProviderForConsumer(target);
+ // TODO(tdresser) - detect gestures eagerly.
+ if (!(result & ER_CONSUMED)) {
+ if (gesture_provider->OnTouchEvent(event)) {
+ gesture_provider->OnTouchEventAck(result != ER_UNHANDLED);
+ return gesture_provider->GetAndResetPendingGestures();
+ }
+ }
+ return NULL;
+ }
}
-void GestureRecognizerImpl::CleanupStateForConsumer(GestureConsumer* consumer) {
- if (consumer_sequence_.count(consumer)) {
- delete consumer_sequence_[consumer];
- consumer_sequence_.erase(consumer);
+bool GestureRecognizerImpl::CleanupStateForConsumer(
+ GestureConsumer* consumer) {
+ bool state_cleaned_up = false;
+
+ if (!use_unified_gesture_detector_) {
+ if (consumer_sequence_.count(consumer)) {
+ state_cleaned_up = true;
+ delete consumer_sequence_[consumer];
+ consumer_sequence_.erase(consumer);
+ }
+ } else {
+ if (consumer_gesture_provider_.count(consumer)) {
+ state_cleaned_up = true;
+ delete consumer_gesture_provider_[consumer];
+ consumer_gesture_provider_.erase(consumer);
+ }
}
- RemoveConsumerFromMap(consumer, &touch_id_target_);
- RemoveConsumerFromMap(consumer, &touch_id_target_for_gestures_);
+ 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) {
@@ -239,12 +348,11 @@ void GestureRecognizerImpl::RemoveGestureEventHelper(
}
void GestureRecognizerImpl::DispatchPostponedGestureEvent(GestureEvent* event) {
- GestureConsumer* consumer = GetTargetForGestureEvent(*event);
- if (consumer) {
- GestureEventHelper* helper = FindDispatchHelperForConsumer(consumer);
- if (helper)
- helper->DispatchPostponedGestureEvent(event);
- }
+ DispatchGestureEvent(event);
+}
+
+void GestureRecognizerImpl::OnGestureEvent(GestureEvent* event) {
+ DispatchGestureEvent(event);
}
GestureEventHelper* GestureRecognizerImpl::FindDispatchHelperForConsumer(
@@ -271,6 +379,12 @@ GestureRecognizer* GestureRecognizer::Get() {
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 =
diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl.h b/chromium/ui/events/gestures/gesture_recognizer_impl.h
index 59c92cfb4a4..80a3007030d 100644
--- a/chromium/ui/events/gestures/gesture_recognizer_impl.h
+++ b/chromium/ui/events/gestures/gesture_recognizer_impl.h
@@ -12,6 +12,7 @@
#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/events/gestures/gesture_sequence.h"
#include "ui/gfx/point.h"
@@ -23,8 +24,12 @@ class GestureEventHelper;
class GestureSequence;
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 GestureSequenceDelegate {
+ public GestureSequenceDelegate,
+ public GestureProviderAuraClient {
public:
typedef std::map<int, GestureConsumer*> TouchIdToConsumerMap;
@@ -39,39 +44,49 @@ class EVENTS_EXPORT GestureRecognizerImpl : public GestureRecognizer,
virtual GestureConsumer* GetTargetForGestureEvent(
const GestureEvent& event) OVERRIDE;
virtual GestureConsumer* GetTargetForLocation(
- const gfx::Point& location) OVERRIDE;
+ 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::Point* point) OVERRIDE;
- virtual void CancelActiveTouches(GestureConsumer* consumer) OVERRIDE;
+ gfx::PointF* point) OVERRIDE;
+ virtual bool CancelActiveTouches(GestureConsumer* consumer) OVERRIDE;
protected:
- virtual GestureSequence* CreateSequence(GestureSequenceDelegate* delegate);
virtual GestureSequence* GetGestureSequenceForConsumer(GestureConsumer* c);
+ virtual GestureProviderAura* GetGestureProviderForConsumer(
+ GestureConsumer* c);
+ virtual GestureSequence* CreateSequence(
+ ui::GestureSequenceDelegate* delegate);
private:
// Sets up the target consumer for gestures based on the touch-event.
void SetupTargets(const TouchEvent& event, GestureConsumer* consumer);
void CancelTouches(std::vector<std::pair<int, GestureConsumer*> >* touches);
+ void DispatchGestureEvent(GestureEvent* event);
+
// Overridden from GestureRecognizer
virtual Gestures* ProcessTouchEventForGesture(
const TouchEvent& event,
ui::EventResult result,
GestureConsumer* target) OVERRIDE;
- virtual void CleanupStateForConsumer(GestureConsumer* consumer) OVERRIDE;
+ virtual bool CleanupStateForConsumer(GestureConsumer* consumer)
+ OVERRIDE;
virtual void AddGestureEventHelper(GestureEventHelper* helper) OVERRIDE;
virtual void RemoveGestureEventHelper(GestureEventHelper* helper) OVERRIDE;
// Overridden from ui::GestureSequenceDelegate.
virtual void DispatchPostponedGestureEvent(GestureEvent* event) 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*, GestureSequence*> consumer_sequence_;
+ 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
@@ -82,6 +97,8 @@ class EVENTS_EXPORT GestureRecognizerImpl : public GestureRecognizer,
std::vector<GestureEventHelper*> helpers_;
+ bool use_unified_gesture_detector_;
+
DISALLOW_COPY_AND_ASSIGN(GestureRecognizerImpl);
};
diff --git a/chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc b/chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc
new file mode 100644
index 00000000000..46dde02fad9
--- /dev/null
+++ b/chromium/ui/events/gestures/gesture_recognizer_impl_mac.cc
@@ -0,0 +1,64 @@
+// 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 Gestures* ProcessTouchEventForGesture(
+ 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/chromium/ui/events/gestures/gesture_sequence.cc b/chromium/ui/events/gestures/gesture_sequence.cc
index 7c195199454..14824d0f676 100644
--- a/chromium/ui/events/gestures/gesture_sequence.cc
+++ b/chromium/ui/events/gestures/gesture_sequence.cc
@@ -6,6 +6,7 @@
#include <stdlib.h>
#include <cmath>
+#include <limits>
#include "base/command_line.h"
#include "base/logging.h"
@@ -16,7 +17,6 @@
#include "ui/events/event_constants.h"
#include "ui/events/event_switches.h"
#include "ui/events/gestures/gesture_configuration.h"
-#include "ui/events/gestures/gesture_util.h"
#include "ui/gfx/rect.h"
namespace ui {
@@ -29,7 +29,6 @@ enum TouchState {
TS_RELEASED,
TS_PRESSED,
TS_MOVED,
- TS_STATIONARY,
TS_CANCELLED,
TS_UNKNOWN,
};
@@ -60,8 +59,6 @@ TouchState TouchEventTypeToTouchState(ui::EventType type) {
return TS_PRESSED;
case ui::ET_TOUCH_MOVED:
return TS_MOVED;
- case ui::ET_TOUCH_STATIONARY:
- return TS_STATIONARY;
case ui::ET_TOUCH_CANCELLED:
return TS_CANCELLED;
default:
@@ -99,9 +96,6 @@ enum EdgeStateSignatureType {
GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED =
G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_MOVED, TSI_PROCESSED),
- GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY =
- G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_STATIONARY, TSI_NOT_PROCESSED),
-
GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED =
G(GS_PENDING_SYNTHETIC_CLICK, 0, TS_CANCELLED, TSI_ALWAYS),
@@ -120,21 +114,26 @@ enum EdgeStateSignatureType {
GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED =
G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 0, TS_MOVED, TSI_ALWAYS),
- GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_STATIONARY =
- G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 0, TS_STATIONARY, TSI_ALWAYS),
-
GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED =
G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 0, TS_CANCELLED, TSI_ALWAYS),
GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED =
G(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL, 1, TS_PRESSED, TSI_NOT_PROCESSED),
+ GST_SYNTHETIC_CLICK_ABORTED_FIRST_RELEASED =
+ G(GS_SYNTHETIC_CLICK_ABORTED, 0, TS_RELEASED, TSI_ALWAYS),
+
+ GST_SYNTHETIC_CLICK_ABORTED_SECOND_PRESSED =
+ G(GS_SYNTHETIC_CLICK_ABORTED, 1, TS_PRESSED, TSI_NOT_PROCESSED),
+
GST_SCROLL_FIRST_RELEASED =
G(GS_SCROLL, 0, TS_RELEASED, TSI_ALWAYS),
- // Once scroll has started, process all touch-move events.
GST_SCROLL_FIRST_MOVED =
- G(GS_SCROLL, 0, TS_MOVED, TSI_ALWAYS),
+ G(GS_SCROLL, 0, TS_MOVED, TSI_NOT_PROCESSED),
+
+ GST_SCROLL_FIRST_MOVED_HANDLED =
+ G(GS_SCROLL, 0, TS_MOVED, TSI_PROCESSED),
GST_SCROLL_FIRST_CANCELLED =
G(GS_SCROLL, 0, TS_CANCELLED, TSI_ALWAYS),
@@ -333,17 +332,18 @@ EdgeStateSignatureType Signature(GestureState gesture_state,
case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED:
case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED:
case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED:
- case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY:
case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED:
case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED:
case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED:
case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED_HANDLED:
case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED:
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_STATIONARY:
case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED:
case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED:
+ case GST_SYNTHETIC_CLICK_ABORTED_FIRST_RELEASED:
+ case GST_SYNTHETIC_CLICK_ABORTED_SECOND_PRESSED:
case GST_SCROLL_FIRST_RELEASED:
case GST_SCROLL_FIRST_MOVED:
+ case GST_SCROLL_FIRST_MOVED_HANDLED:
case GST_SCROLL_FIRST_CANCELLED:
case GST_SCROLL_SECOND_PRESSED:
case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED:
@@ -412,7 +412,7 @@ EdgeStateSignatureType Signature(GestureState gesture_state,
}
#undef G
-float BoundingBoxDiagonal(const gfx::Rect& rect) {
+float BoundingBoxDiagonal(const gfx::RectF& rect) {
float width = rect.width() * rect.width();
float height = rect.height() * rect.height();
return sqrt(width + height);
@@ -449,28 +449,28 @@ float CalibrateFlingVelocity(float velocity) {
void UpdateGestureEventLatencyInfo(const TouchEvent& event,
GestureSequence::Gestures* gestures) {
- // If the touch event does not cause any rendering scheduled, we first
- // end the touch event's LatencyInfo. Then we copy the touch event's
- // LatencyInfo into the generated gesture's LatencyInfo. Since one touch
- // event can generate multiple gesture events, we have to clear the gesture
- // event's trace_id, remove its ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
- // so when the gesture event passes through RWHI, a new trace_id will be
- // assigned and new ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT will be added.
- if (!event.latency()->FindLatency(
- ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT, 0, NULL)) {
- ui::LatencyInfo* touch_latency =
- const_cast<ui::LatencyInfo*>(event.latency());
- touch_latency->AddLatencyNumber(
- ui::INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT, 0, 0);
- GestureSequence::Gestures::iterator it = gestures->begin();
- for (; it != gestures->end(); it++) {
- ui::LatencyInfo* gesture_latency = (*it)->latency();
- *gesture_latency = *touch_latency;
- gesture_latency->trace_id = -1;
- gesture_latency->terminated = false;
- gesture_latency->RemoveLatency(
- ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT);
- }
+ // Copy some of the touch event's LatencyInfo into the generated gesture's
+ // LatencyInfo so we can compute touch to scroll latency from gesture
+ // event's LatencyInfo.
+ GestureSequence::Gestures::iterator it = gestures->begin();
+ for (; it != gestures->end(); it++) {
+ ui::LatencyInfo* gesture_latency = (*it)->latency();
+ gesture_latency->CopyLatencyFrom(
+ *event.latency(), ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
+ gesture_latency->CopyLatencyFrom(
+ *event.latency(), ui::INPUT_EVENT_LATENCY_UI_COMPONENT);
+ gesture_latency->CopyLatencyFrom(
+ *event.latency(), ui::INPUT_EVENT_LATENCY_ACKED_TOUCH_COMPONENT);
+ }
+}
+
+bool GestureStateSupportsActiveTimer(GestureState state) {
+ switch(state) {
+ case GS_PENDING_SYNTHETIC_CLICK:
+ case GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL:
+ return true;
+ default:
+ return false;
}
}
@@ -520,6 +520,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
}
new_point->set_point_id(point_count_++);
new_point->set_touch_id(event.touch_id());
+ new_point->set_source_device_id(event.source_device_id());
}
GestureState last_state = state_;
@@ -556,6 +557,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
set_state(GS_PENDING_SYNTHETIC_CLICK);
break;
case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED:
+ case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED:
if (Click(event, point, gestures.get()))
point.UpdateForTap();
else
@@ -563,39 +565,41 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
set_state(GS_NO_GESTURE);
break;
case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED:
- case GST_PENDING_SYNTHETIC_CLICK_FIRST_STATIONARY:
if (ScrollStart(event, point, gestures.get())) {
PrependTapCancelGestureEvent(point, gestures.get());
set_state(GS_SCROLL);
- if (ScrollUpdate(event, point, gestures.get()))
+ if (ScrollUpdate(event, point, gestures.get(), FS_FIRST_SCROLL))
point.UpdateForScroll();
}
break;
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED:
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_STATIONARY:
- // No scrolling allowed, so nothing happens.
- break;
case GST_PENDING_SYNTHETIC_CLICK_FIRST_MOVED_PROCESSED:
+ case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_MOVED:
if (point.IsInScrollWindow(event)) {
PrependTapCancelGestureEvent(point, gestures.get());
+ set_state(GS_SYNTHETIC_CLICK_ABORTED);
+ } else {
set_state(GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL);
}
break;
case GST_PENDING_SYNTHETIC_CLICK_FIRST_RELEASED_HANDLED:
case GST_PENDING_SYNTHETIC_CLICK_FIRST_CANCELLED:
+ case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED_HANDLED:
+ case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED:
PrependTapCancelGestureEvent(point, gestures.get());
set_state(GS_NO_GESTURE);
break;
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED:
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_RELEASED_HANDLED:
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_FIRST_CANCELLED:
+ case GST_SYNTHETIC_CLICK_ABORTED_FIRST_RELEASED:
set_state(GS_NO_GESTURE);
break;
case GST_SCROLL_FIRST_MOVED:
if (scroll_type_ == ST_VERTICAL ||
scroll_type_ == ST_HORIZONTAL)
BreakRailScroll(event, point, gestures.get());
- if (ScrollUpdate(event, point, gestures.get()))
+ if (ScrollUpdate(event, point, gestures.get(), FS_NOT_FIRST_SCROLL))
+ point.UpdateForScroll();
+ break;
+ case GST_SCROLL_FIRST_MOVED_HANDLED:
+ if (point.DidScroll(event, 0))
point.UpdateForScroll();
break;
case GST_SCROLL_FIRST_RELEASED:
@@ -604,10 +608,11 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
set_state(GS_NO_GESTURE);
break;
case GST_PENDING_SYNTHETIC_CLICK_SECOND_PRESSED:
+ case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED:
PrependTapCancelGestureEvent(point, gestures.get());
TwoFingerTapOrPinch(event, point, gestures.get());
break;
- case GST_PENDING_SYNTHETIC_CLICK_NO_SCROLL_SECOND_PRESSED:
+ case GST_SYNTHETIC_CLICK_ABORTED_SECOND_PRESSED:
TwoFingerTapOrPinch(event, point, gestures.get());
break;
case GST_SCROLL_SECOND_PRESSED:
@@ -617,7 +622,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
case GST_PENDING_TWO_FINGER_TAP_FIRST_RELEASED:
case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED:
TwoFingerTouchReleased(event, point, gestures.get());
- set_state(GS_SCROLL);
+ StartRailFreeScroll(point, gestures.get());
break;
case GST_PENDING_TWO_FINGER_TAP_FIRST_MOVED:
case GST_PENDING_TWO_FINGER_TAP_SECOND_MOVED:
@@ -632,8 +637,7 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
case GST_PENDING_TWO_FINGER_TAP_SECOND_RELEASED_HANDLED:
case GST_PENDING_TWO_FINGER_TAP_FIRST_CANCELLED:
case GST_PENDING_TWO_FINGER_TAP_SECOND_CANCELLED:
- scroll_type_ = ST_FREE;
- set_state(GS_SCROLL);
+ StartRailFreeScroll(point, gestures.get());
break;
case GST_PENDING_TWO_FINGER_TAP_THIRD_PRESSED:
set_state(GS_PENDING_PINCH);
@@ -645,18 +649,17 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_FIRST_RELEASED:
case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_SECOND_RELEASED:
TwoFingerTouchReleased(event, point, gestures.get());
- // We transit into GS_SCROLL even though the touch move can be
- // consumed and no scroll should happen. crbug.com/240399.
- set_state(GS_SCROLL);
+ // We transition into GS_SCROLL even though the touch move can be consumed
+ // and no scroll should happen. crbug.com/240399.
+ StartRailFreeScroll(point, gestures.get());
break;
case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_FIRST_RELEASED_HANDLED:
case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_SECOND_RELEASED_HANDLED:
case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_FIRST_CANCELLED:
case GST_PENDING_TWO_FINGER_TAP_NO_PINCH_SECOND_CANCELLED:
- // We transit into GS_SCROLL even though the touch move can be
- // consumed and no scroll should happen. crbug.com/240399.
- scroll_type_ = ST_FREE;
- set_state(GS_SCROLL);
+ // We transition into GS_SCROLL even though the touch move can be consumed
+ // and no scroll should happen. crbug.com/240399.
+ StartRailFreeScroll(point, gestures.get());
break;
case GST_PENDING_PINCH_FIRST_MOVED:
case GST_PENDING_PINCH_SECOND_MOVED:
@@ -671,10 +674,9 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
case GST_PENDING_PINCH_SECOND_RELEASED:
case GST_PENDING_PINCH_FIRST_CANCELLED:
case GST_PENDING_PINCH_SECOND_CANCELLED:
- // We transit into GS_SCROLL even though the touch move can be
- // consumed and no scroll should happen. crbug.com/240399.
- scroll_type_ = ST_FREE;
- set_state(GS_SCROLL);
+ // We transition into GS_SCROLL even though the touch move can be consumed
+ // and no scroll should happen. crbug.com/240399.
+ StartRailFreeScroll(point, gestures.get());
break;
case GST_PENDING_PINCH_NO_PINCH_FIRST_MOVED:
case GST_PENDING_PINCH_NO_PINCH_SECOND_MOVED:
@@ -684,16 +686,19 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
case GST_PENDING_PINCH_NO_PINCH_SECOND_RELEASED:
case GST_PENDING_PINCH_NO_PINCH_FIRST_CANCELLED:
case GST_PENDING_PINCH_NO_PINCH_SECOND_CANCELLED:
- // We transit into GS_SCROLL even though the touch move can be
- // consumed and no scroll should happen. crbug.com/240399.
- scroll_type_ = ST_FREE;
- set_state(GS_SCROLL);
+ // We transition into GS_SCROLL even though the touch move can be consumed
+ // and no scroll should happen. crbug.com/240399.
+ StartRailFreeScroll(point, gestures.get());
break;
case GST_PINCH_FIRST_MOVED_HANDLED:
case GST_PINCH_SECOND_MOVED_HANDLED:
case GST_PINCH_THIRD_MOVED_HANDLED:
case GST_PINCH_FOURTH_MOVED_HANDLED:
case GST_PINCH_FIFTH_MOVED_HANDLED:
+ // If touches are consumed for a while, and then left unconsumed, we don't
+ // want a PinchUpdate or ScrollUpdate with a massive delta.
+ latest_multi_scroll_update_location_ = bounding_box_.CenterPoint();
+ pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_);
break;
case GST_PINCH_FIRST_MOVED:
case GST_PINCH_SECOND_MOVED:
@@ -750,7 +755,10 @@ GestureSequence::Gestures* GestureSequence::ProcessTouchEventForGesture(
<< " State: " << state_
<< " touch id: " << event.touch_id();
- if (last_state == GS_PENDING_SYNTHETIC_CLICK && state_ != last_state) {
+ // If the state has changed from one in which a long/show press is possible to
+ // one in which they are not possible, cancel the timers.
+ if (GestureStateSupportsActiveTimer(last_state) &&
+ !GestureStateSupportsActiveTimer(state_)) {
GetLongPressTimer()->Stop();
GetShowPressTimer()->Stop();
}
@@ -789,8 +797,10 @@ void GestureSequence::RecreateBoundingBox() {
} else if (point_count_ == 1) {
bounding_box_ = GetPointByPointId(0)->enclosing_rectangle();
} else {
- int left = INT_MAX / 20, top = INT_MAX / 20;
- int right = INT_MIN / 20, bottom = INT_MIN / 20;
+ 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 (int i = 0; i < kMaxGesturePoints; ++i) {
if (!points_[i].in_use())
continue;
@@ -798,7 +808,7 @@ void GestureSequence::RecreateBoundingBox() {
// However, this becomes brittle especially when a finger is in motion
// because the change in radius can overshadow the actual change in
// position. So the actual position of the point is used instead.
- const gfx::Point& point = points_[i].last_touch_position();
+ const gfx::PointF& point = points_[i].last_touch_position();
left = std::min(left, point.x());
right = std::max(right, point.x());
top = std::min(top, point.y());
@@ -854,8 +864,8 @@ GesturePoint* GestureSequence::GetPointByPointId(int point_id) {
}
bool GestureSequence::IsSecondTouchDownCloseEnoughForTwoFingerTap() {
- gfx::Point p1 = GetPointByPointId(0)->last_touch_position();
- gfx::Point p2 = GetPointByPointId(1)->last_touch_position();
+ gfx::PointF p1 = GetPointByPointId(0)->last_touch_position();
+ gfx::PointF p2 = GetPointByPointId(1)->last_touch_position();
double max_distance =
GestureConfiguration::max_distance_for_two_finger_tap_in_pixels();
double distance = (p1.x() - p2.x()) * (p1.x() - p2.x()) +
@@ -867,7 +877,7 @@ bool GestureSequence::IsSecondTouchDownCloseEnoughForTwoFingerTap() {
GestureEvent* GestureSequence::CreateGestureEvent(
const GestureEventDetails& details,
- const gfx::Point& location,
+ const gfx::PointF& location,
int flags,
base::Time timestamp,
unsigned int touch_id_bitmask) {
@@ -915,7 +925,7 @@ void GestureSequence::AppendEndGestureEvent(const GesturePoint& point,
Gestures* gestures) {
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_GESTURE_END, 0, 0),
- point.first_touch_position(),
+ point.last_touch_position(),
flags_,
base::Time::FromDoubleT(point.last_touch_time()),
1 << point.touch_id()));
@@ -924,8 +934,8 @@ void GestureSequence::AppendEndGestureEvent(const GesturePoint& point,
void GestureSequence::AppendClickGestureEvent(const GesturePoint& point,
int tap_count,
Gestures* gestures) {
- gfx::Rect er = point.enclosing_rectangle();
- gfx::Point center = er.CenterPoint();
+ gfx::RectF er = point.enclosing_rectangle();
+ gfx::PointF center = er.CenterPoint();
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_GESTURE_TAP, tap_count, 0),
center,
@@ -935,10 +945,11 @@ void GestureSequence::AppendClickGestureEvent(const GesturePoint& point,
}
void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point,
- const gfx::Point& location,
+ const gfx::PointF& location,
Gestures* gestures) {
+ gfx::Vector2dF d = point.ScrollDelta();
gestures->push_back(CreateGestureEvent(
- GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, 0, 0),
+ GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN, d.x(), d.y()),
location,
flags_,
base::Time::FromDoubleT(point.last_touch_time()),
@@ -946,7 +957,7 @@ void GestureSequence::AppendScrollGestureBegin(const GesturePoint& point,
}
void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point,
- const gfx::Point& location,
+ const gfx::PointF& location,
Gestures* gestures,
float x_velocity,
float y_velocity) {
@@ -965,9 +976,7 @@ void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point,
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_SCROLL_FLING_START,
CalibrateFlingVelocity(railed_x_velocity),
- CalibrateFlingVelocity(railed_y_velocity),
- CalibrateFlingVelocity(x_velocity),
- CalibrateFlingVelocity(y_velocity)),
+ CalibrateFlingVelocity(railed_y_velocity)),
location,
flags_,
base::Time::FromDoubleT(point.last_touch_time()),
@@ -983,11 +992,12 @@ void GestureSequence::AppendScrollGestureEnd(const GesturePoint& point,
}
void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point,
- Gestures* gestures) {
+ Gestures* gestures,
+ IsFirstScroll is_first_scroll) {
static bool use_scroll_prediction = CommandLine::ForCurrentProcess()->
HasSwitch(switches::kEnableScrollPrediction);
gfx::Vector2dF d;
- gfx::Point location;
+ gfx::PointF location;
if (point_count_ == 1) {
d = point.ScrollDelta();
location = point.last_touch_position();
@@ -1006,11 +1016,18 @@ void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point,
last_scroll_prediction_offset_.set_y(
GestureConfiguration::scroll_prediction_seconds() * point.YVelocity());
d += last_scroll_prediction_offset_;
- location += gfx::Vector2d(last_scroll_prediction_offset_.x(),
- last_scroll_prediction_offset_.y());
+ location += gfx::Vector2dF(last_scroll_prediction_offset_.x(),
+ last_scroll_prediction_offset_.y());
}
- gfx::Vector2dF o = d;
+ if (is_first_scroll == FS_FIRST_SCROLL) {
+ float slop = GestureConfiguration::max_touch_move_in_pixels_for_click();
+ float length = d.Length();
+ float ratio = std::max((length - slop) / length, 0.0f);
+
+ d.set_x(d.x() * ratio);
+ d.set_y(d.y() * ratio);
+ }
if (scroll_type_ == ST_HORIZONTAL)
d.set_y(0);
@@ -1019,13 +1036,7 @@ void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point,
if (d.IsZero())
return;
- GestureEventDetails details(ui::ET_GESTURE_SCROLL_UPDATE,
- d.x(), d.y(), o.x(), o.y());
- details.SetScrollVelocity(
- scroll_type_ == ST_VERTICAL ? 0 : point.XVelocity(),
- scroll_type_ == ST_HORIZONTAL ? 0 : point.YVelocity(),
- point.XVelocity(),
- point.YVelocity());
+ GestureEventDetails details(ui::ET_GESTURE_SCROLL_UPDATE, d.x(), d.y());
gestures->push_back(CreateGestureEvent(
details,
location,
@@ -1037,7 +1048,7 @@ void GestureSequence::AppendScrollGestureUpdate(GesturePoint& point,
void GestureSequence::AppendPinchGestureBegin(const GesturePoint& p1,
const GesturePoint& p2,
Gestures* gestures) {
- gfx::Point center = bounding_box_.CenterPoint();
+ gfx::PointF center = bounding_box_.CenterPoint();
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_GESTURE_PINCH_BEGIN, 0, 0),
center,
@@ -1050,7 +1061,7 @@ void GestureSequence::AppendPinchGestureEnd(const GesturePoint& p1,
const GesturePoint& p2,
float scale,
Gestures* gestures) {
- gfx::Point center = bounding_box_.CenterPoint();
+ gfx::PointF center = bounding_box_.CenterPoint();
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_GESTURE_PINCH_END, 0, 0),
center,
@@ -1077,7 +1088,7 @@ void GestureSequence::AppendSwipeGesture(const GesturePoint& point,
int swipe_y,
Gestures* gestures) {
gestures->push_back(CreateGestureEvent(
- GestureEventDetails(ui::ET_GESTURE_MULTIFINGER_SWIPE, swipe_x, swipe_y),
+ GestureEventDetails(ui::ET_GESTURE_SWIPE, swipe_x, swipe_y),
bounding_box_.CenterPoint(),
flags_,
base::Time::FromDoubleT(point.last_touch_time()),
@@ -1086,7 +1097,7 @@ void GestureSequence::AppendSwipeGesture(const GesturePoint& point,
void GestureSequence::AppendTwoFingerTapGestureEvent(Gestures* gestures) {
const GesturePoint* point = GetPointByPointId(0);
- const gfx::Rect rect = point->enclosing_rectangle();
+ const gfx::RectF& rect = point->enclosing_rectangle();
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_GESTURE_TWO_FINGER_TAP,
rect.width(),
@@ -1100,7 +1111,8 @@ void GestureSequence::AppendTwoFingerTapGestureEvent(Gestures* gestures) {
bool GestureSequence::Click(const TouchEvent& event,
const GesturePoint& point,
Gestures* gestures) {
- DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK);
+ DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK ||
+ state_ == GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL);
if (point.IsInClickWindow(event)) {
int tap_count = 1;
if (point.IsInTripleClickWindow(event))
@@ -1113,7 +1125,7 @@ bool GestureSequence::Click(const TouchEvent& event,
}
AppendClickGestureEvent(point, tap_count, gestures);
return true;
- } else if (point.IsInsideManhattanSquare(event) &&
+ } else if (point.IsInsideTouchSlopRegion(event) &&
!GetLongPressTimer()->IsRunning()) {
AppendLongTapGestureEvent(point, gestures);
}
@@ -1150,11 +1162,12 @@ void GestureSequence::BreakRailScroll(const TouchEvent& event,
bool GestureSequence::ScrollUpdate(const TouchEvent& event,
GesturePoint& point,
- Gestures* gestures) {
+ Gestures* gestures,
+ IsFirstScroll is_first_scroll) {
DCHECK(state_ == GS_SCROLL);
if (!point.DidScroll(event, 0))
return false;
- AppendScrollGestureUpdate(point, gestures);
+ AppendScrollGestureUpdate(point, gestures, is_first_scroll);
return true;
}
@@ -1185,11 +1198,13 @@ bool GestureSequence::TwoFingerTouchDown(const TouchEvent& event,
Gestures* gestures) {
DCHECK(state_ == GS_PENDING_SYNTHETIC_CLICK ||
state_ == GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL ||
+ state_ == GS_SYNTHETIC_CLICK_ABORTED ||
state_ == GS_SCROLL);
if (state_ == GS_SCROLL) {
- AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
- 0.f, 0.f);
+ AppendScrollGestureEnd(point,
+ point.last_touch_position(),
+ gestures, 0.f, 0.f);
}
second_touch_time_ = event.time_stamp();
return true;
@@ -1204,7 +1219,7 @@ bool GestureSequence::TwoFingerTouchMove(const TouchEvent& event,
base::TimeDelta time_delta = event.time_stamp() - second_touch_time_;
base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 *
ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click());
- if (time_delta > max_delta || !point.IsInsideManhattanSquare(event)) {
+ if (time_delta > max_delta || !point.IsInsideTouchSlopRegion(event)) {
PinchStart(event, point, gestures);
return true;
}
@@ -1219,7 +1234,7 @@ bool GestureSequence::TwoFingerTouchReleased(const TouchEvent& event,
base::TimeDelta time_delta = event.time_stamp() - second_touch_time_;
base::TimeDelta max_delta = base::TimeDelta::FromMilliseconds(1000 *
ui::GestureConfiguration::max_touch_down_duration_in_seconds_for_click());
- if (time_delta < max_delta && point.IsInsideManhattanSquare(event))
+ if (time_delta < max_delta && point.IsInsideTouchSlopRegion(event))
AppendTwoFingerTapGestureEvent(gestures);
return true;
}
@@ -1248,11 +1263,9 @@ void GestureSequence::AppendShowPressGestureEvent() {
void GestureSequence::AppendLongTapGestureEvent(const GesturePoint& point,
Gestures* gestures) {
- gfx::Rect er = point.enclosing_rectangle();
- gfx::Point center = er.CenterPoint();
gestures->push_back(CreateGestureEvent(
GestureEventDetails(ui::ET_GESTURE_LONG_TAP, 0, 0),
- center,
+ point.enclosing_rectangle().CenterPoint(),
flags_,
base::Time::FromDoubleT(point.last_touch_time()),
1 << point.touch_id()));
@@ -1263,11 +1276,14 @@ bool GestureSequence::ScrollEnd(const TouchEvent& event,
Gestures* gestures) {
DCHECK(state_ == GS_SCROLL);
if (point.IsInFlickWindow(event)) {
- AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
- point.XVelocity(), point.YVelocity());
+ AppendScrollGestureEnd(point,
+ point.last_touch_position(),
+ gestures,
+ point.XVelocity(), point.YVelocity());
} else {
- AppendScrollGestureEnd(point, point.last_touch_position(), gestures,
- 0.f, 0.f);
+ AppendScrollGestureEnd(point,
+ point.last_touch_position(),
+ gestures, 0.f, 0.f);
}
return true;
}
@@ -1285,17 +1301,16 @@ bool GestureSequence::PinchStart(const TouchEvent& event,
const GesturePoint* point1 = GetPointByPointId(0);
const GesturePoint* point2 = GetPointByPointId(1);
+ if (state_ == GS_PENDING_TWO_FINGER_TAP ||
+ state_ == GS_PENDING_PINCH) {
+ AppendScrollGestureBegin(point, bounding_box_.CenterPoint(), gestures);
+ }
+
pinch_distance_current_ = BoundingBoxDiagonal(bounding_box_);
pinch_distance_start_ = pinch_distance_current_;
latest_multi_scroll_update_location_ = bounding_box_.CenterPoint();
AppendPinchGestureBegin(*point1, *point2, gestures);
- if (state_ == GS_PENDING_TWO_FINGER_TAP ||
- state_ == GS_PENDING_PINCH) {
- gfx::Point center = bounding_box_.CenterPoint();
- AppendScrollGestureBegin(point, center, gestures);
- }
-
return true;
}
@@ -1322,13 +1337,13 @@ bool GestureSequence::PinchUpdate(const TouchEvent& event,
float distance = BoundingBoxDiagonal(bounding_box_);
- if (abs(distance - pinch_distance_current_) >=
+ if (std::abs(distance - pinch_distance_current_) >=
GestureConfiguration::min_pinch_update_distance_in_pixels()) {
AppendPinchGestureUpdate(point,
distance / pinch_distance_current_, gestures);
pinch_distance_current_ = distance;
}
- AppendScrollGestureUpdate(point, gestures);
+ AppendScrollGestureUpdate(point, gestures, FS_NOT_FIRST_SCROLL);
return true;
}
@@ -1385,7 +1400,6 @@ bool GestureSequence::MaybeSwipe(const TouchEvent& event,
}
float min_velocity = GestureConfiguration::min_swipe_speed();
- min_velocity *= min_velocity;
velocity_x = fabs(velocity_x / point_count_);
velocity_y = fabs(velocity_y / point_count_);
@@ -1437,11 +1451,17 @@ void GestureSequence::StopTimersIfRequired(const TouchEvent& event) {
// Since a timer is running, there should be a non-NULL point.
const GesturePoint* point = GetPointByPointId(0);
- if (!ui::gestures::IsInsideManhattanSquare(point->first_touch_position(),
- event.location())) {
+ if (!point->IsInsideTouchSlopRegion(event)) {
GetLongPressTimer()->Stop();
GetShowPressTimer()->Stop();
}
}
+void GestureSequence::StartRailFreeScroll(const GesturePoint& point,
+ Gestures* gestures) {
+ AppendScrollGestureBegin(point, point.first_touch_position(), gestures);
+ scroll_type_ = ST_FREE;
+ set_state(GS_SCROLL);
+}
+
} // namespace ui
diff --git a/chromium/ui/events/gestures/gesture_sequence.h b/chromium/ui/events/gestures/gesture_sequence.h
index b3255df573c..62676e940ae 100644
--- a/chromium/ui/events/gestures/gesture_sequence.h
+++ b/chromium/ui/events/gestures/gesture_sequence.h
@@ -7,6 +7,7 @@
#include "base/timer/timer.h"
#include "ui/events/event_constants.h"
+#include "ui/events/gesture_event_details.h"
#include "ui/events/gestures/gesture_point.h"
#include "ui/events/gestures/gesture_recognizer.h"
#include "ui/gfx/rect.h"
@@ -19,7 +20,12 @@ class GestureEvent;
enum GestureState {
GS_NO_GESTURE,
GS_PENDING_SYNTHETIC_CLICK,
+ // One finger is down: tap could occur, but scroll cannot until the number of
+ // active touch points changes.
GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL,
+ // One finger is down: no gestures can occur until the number of active touch
+ // points changes.
+ GS_SYNTHETIC_CLICK_ABORTED,
GS_SCROLL,
GS_PINCH,
GS_PENDING_TWO_FINGER_TAP,
@@ -34,6 +40,11 @@ enum ScrollType {
ST_VERTICAL,
};
+enum IsFirstScroll {
+ FS_FIRST_SCROLL,
+ FS_NOT_FIRST_SCROLL,
+};
+
// Delegates dispatch of gesture events for which the GestureSequence does not
// have enough context to dispatch itself.
class EVENTS_EXPORT GestureSequenceDelegate {
@@ -64,7 +75,9 @@ class EVENTS_EXPORT GestureSequence {
const GesturePoint* points() const { return points_; }
int point_count() const { return point_count_; }
- const gfx::Point& last_touch_location() const { return last_touch_location_; }
+ const gfx::PointF& last_touch_location() const {
+ return last_touch_location_;
+ }
protected:
virtual base::OneShotTimer<GestureSequence>* CreateTimer();
@@ -90,7 +103,7 @@ class EVENTS_EXPORT GestureSequence {
// includes some common information (e.g. number of touch-points in the
// gesture etc.) in the gesture event as well.
GestureEvent* CreateGestureEvent(const GestureEventDetails& details,
- const gfx::Point& location,
+ const gfx::PointF& location,
int flags,
base::Time timestamp,
unsigned int touch_id_bitmask);
@@ -115,15 +128,16 @@ class EVENTS_EXPORT GestureSequence {
// Scroll gestures.
void AppendScrollGestureBegin(const GesturePoint& point,
- const gfx::Point& location,
+ const gfx::PointF& location,
Gestures* gestures);
void AppendScrollGestureEnd(const GesturePoint& point,
- const gfx::Point& location,
+ const gfx::PointF& location,
Gestures* gestures,
float x_velocity,
float y_velocity);
void AppendScrollGestureUpdate(GesturePoint& point,
- Gestures* gestures);
+ Gestures* gestures,
+ IsFirstScroll is_first_scroll);
// Pinch gestures.
void AppendPinchGestureBegin(const GesturePoint& p1,
@@ -158,7 +172,8 @@ class EVENTS_EXPORT GestureSequence {
Gestures* gestures);
bool ScrollUpdate(const TouchEvent& event,
GesturePoint& point,
- Gestures* gestures);
+ Gestures* gestures,
+ IsFirstScroll is_first_scroll);
bool TouchDown(const TouchEvent& event,
const GesturePoint& point,
Gestures* gestures);
@@ -193,6 +208,8 @@ class EVENTS_EXPORT GestureSequence {
void StopTimersIfRequired(const TouchEvent& event);
+ void StartRailFreeScroll(const GesturePoint& point, Gestures* gestures);
+
// Current state of gesture recognizer.
GestureState state_;
@@ -201,11 +218,11 @@ class EVENTS_EXPORT GestureSequence {
// We maintain the smallest axis-aligned rectangle that contains all the
// current touch-points. This box is updated after every touch-event.
- gfx::Rect bounding_box_;
+ gfx::RectF bounding_box_;
// The center of the bounding box used in the latest multi-finger scroll
// update gesture.
- gfx::Point latest_multi_scroll_update_location_;
+ gfx::PointF latest_multi_scroll_update_location_;
// The last scroll update prediction offset. This is removed from the scroll
// distance on the next update since the page has already been scrolled this
@@ -233,7 +250,7 @@ class EVENTS_EXPORT GestureSequence {
int point_count_;
// Location of the last touch event.
- gfx::Point last_touch_location_;
+ gfx::PointF last_touch_location_;
GestureSequenceDelegate* delegate_;
diff --git a/chromium/ui/events/gestures/gesture_types.cc b/chromium/ui/events/gestures/gesture_types.cc
deleted file mode 100644
index da91bd07db0..00000000000
--- a/chromium/ui/events/gestures/gesture_types.cc
+++ /dev/null
@@ -1,105 +0,0 @@
-// 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_types.h"
-
-namespace ui {
-
-GestureEventDetails::GestureEventDetails(ui::EventType type,
- float delta_x,
- float delta_y)
- : type_(type),
- touch_points_(1) {
- switch (type_) {
- case ui::ET_GESTURE_SCROLL_UPDATE:
- data.scroll_update.x = delta_x;
- data.scroll_update.y = delta_y;
- data.scroll_update.x_ordinal = delta_x;
- data.scroll_update.y_ordinal = delta_y;
- break;
-
- case ui::ET_SCROLL_FLING_START:
- data.fling_velocity.x = delta_x;
- data.fling_velocity.y = delta_y;
- data.fling_velocity.x_ordinal = delta_x;
- data.fling_velocity.y_ordinal = delta_y;
- break;
-
- case ui::ET_GESTURE_LONG_PRESS:
- data.touch_id = static_cast<int>(delta_x);
- CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for long press.";
- 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_PINCH_UPDATE:
- data.scale = delta_x;
- CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for pinch";
- break;
-
- case ui::ET_GESTURE_MULTIFINGER_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;
-
- case ui::ET_GESTURE_TAP:
- data.tap_count = static_cast<int>(delta_x);
- CHECK_EQ(0.f, delta_y) << "Unknown data in delta_y for tap.";
- break;
-
- default:
- if (delta_x != 0.f || delta_y != 0.f) {
- DLOG(WARNING) << "A gesture event (" << type << ") had unknown data: ("
- << delta_x << "," << delta_y;
- }
- break;
- }
-}
-
-GestureEventDetails::GestureEventDetails(ui::EventType type,
- float delta_x,
- float delta_y,
- float delta_x_ordinal,
- float delta_y_ordinal)
- : type_(type),
- touch_points_(1) {
- CHECK(type == ui::ET_GESTURE_SCROLL_UPDATE ||
- type == ui::ET_SCROLL_FLING_START);
- switch (type_) {
- case ui::ET_GESTURE_SCROLL_UPDATE:
- data.scroll_update.x = delta_x;
- data.scroll_update.y = delta_y;
- data.scroll_update.x_ordinal = delta_x_ordinal;
- data.scroll_update.y_ordinal = delta_y_ordinal;
- break;
-
- case ui::ET_SCROLL_FLING_START:
- data.fling_velocity.x = delta_x;
- data.fling_velocity.y = delta_y;
- data.fling_velocity.x_ordinal = delta_x_ordinal;
- data.fling_velocity.y_ordinal = delta_y_ordinal;
- break;
-
- default:
- break;
- }
-}
-
-void GestureEventDetails::SetScrollVelocity(float velocity_x,
- float velocity_y,
- float velocity_x_ordinal,
- float velocity_y_ordinal) {
- CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_);
- data.scroll_update.velocity_x = velocity_x;
- data.scroll_update.velocity_y = velocity_y;
- data.scroll_update.velocity_x_ordinal = velocity_x_ordinal;
- data.scroll_update.velocity_y_ordinal = velocity_y_ordinal;
-}
-
-} // namespace ui
diff --git a/chromium/ui/events/gestures/gesture_types.h b/chromium/ui/events/gestures/gesture_types.h
index b0670fe8f6d..c15a2fdca47 100644
--- a/chromium/ui/events/gestures/gesture_types.h
+++ b/chromium/ui/events/gestures/gesture_types.h
@@ -5,178 +5,13 @@
#ifndef UI_EVENTS_GESTURES_GESTURE_TYPES_H_
#define UI_EVENTS_GESTURES_GESTURE_TYPES_H_
-#include "base/logging.h"
-#include "ui/events/event_constants.h"
#include "ui/events/events_export.h"
-#include "ui/gfx/rect.h"
namespace ui {
class GestureEvent;
class TouchEvent;
-struct EVENTS_EXPORT GestureEventDetails {
- public:
- GestureEventDetails(EventType type, float delta_x, float delta_y);
- GestureEventDetails(EventType type,
- float delta_x, float delta_y,
- float delta_x_ordinal, float delta_y_ordinal);
-
- EventType type() const { return type_; }
-
- int touch_points() const { return touch_points_; }
- void set_touch_points(int touch_points) { touch_points_ = touch_points; }
-
- const gfx::Rect& bounding_box() const { return bounding_box_; }
- void set_bounding_box(const gfx::Rect& box) { bounding_box_ = box; }
-
- void SetScrollVelocity(float velocity_x, float velocity_y,
- float velocity_x_ordinal, float velocity_y_ordinal);
-
- float scroll_x() const {
- CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_);
- return data.scroll_update.x;
- }
-
- float scroll_y() const {
- CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_);
- return data.scroll_update.y;
- }
-
- float velocity_x() const {
- CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE ||
- type_ == ui::ET_SCROLL_FLING_START);
- return type_ == ui::ET_SCROLL_FLING_START ? data.fling_velocity.x :
- data.scroll_update.velocity_x;
- }
-
- float velocity_y() const {
- CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE ||
- type_ == ui::ET_SCROLL_FLING_START);
- return type_ == ui::ET_SCROLL_FLING_START ? data.fling_velocity.y :
- data.scroll_update.velocity_y;
- }
-
- // *_ordinal values are unmodified by rail based clamping.
- float scroll_x_ordinal() const {
- CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_);
- return data.scroll_update.x_ordinal;
- }
-
- float scroll_y_ordinal() const {
- CHECK_EQ(ui::ET_GESTURE_SCROLL_UPDATE, type_);
- return data.scroll_update.y_ordinal;
- }
-
- float velocity_x_ordinal() const {
- CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE ||
- type_ == ui::ET_SCROLL_FLING_START);
- return type_ == ui::ET_SCROLL_FLING_START ?
- data.fling_velocity.x_ordinal :
- data.scroll_update.velocity_x_ordinal;
- }
-
- float velocity_y_ordinal() const {
- CHECK(type_ == ui::ET_GESTURE_SCROLL_UPDATE ||
- type_ == ui::ET_SCROLL_FLING_START);
- return type_ == ui::ET_SCROLL_FLING_START ?
- data.fling_velocity.y_ordinal :
- data.scroll_update.velocity_y_ordinal;
- }
-
- int touch_id() const {
- CHECK_EQ(ui::ET_GESTURE_LONG_PRESS, type_);
- return data.touch_id;
- }
-
- float first_finger_width() const {
- CHECK_EQ(ui::ET_GESTURE_TWO_FINGER_TAP, type_);
- return data.first_finger_enclosing_rectangle.width;
- }
-
- float first_finger_height() const {
- CHECK_EQ(ui::ET_GESTURE_TWO_FINGER_TAP, type_);
- return data.first_finger_enclosing_rectangle.height;
- }
-
- float scale() const {
- CHECK_EQ(ui::ET_GESTURE_PINCH_UPDATE, type_);
- return data.scale;
- }
-
- bool swipe_left() const {
- CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_);
- return data.swipe.left;
- }
-
- bool swipe_right() const {
- CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_);
- return data.swipe.right;
- }
-
- bool swipe_up() const {
- CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_);
- return data.swipe.up;
- }
-
- bool swipe_down() const {
- CHECK_EQ(ui::ET_GESTURE_MULTIFINGER_SWIPE, type_);
- return data.swipe.down;
- }
-
- int tap_count() const {
- CHECK_EQ(ui::ET_GESTURE_TAP, type_);
- return data.tap_count;
- }
-
- private:
- ui::EventType type_;
- union {
- struct { // SCROLL delta.
- float x;
- float y;
- float velocity_x;
- float velocity_y;
- float x_ordinal;
- float y_ordinal;
- float velocity_x_ordinal;
- float velocity_y_ordinal;
- } scroll_update;
-
- float scale; // PINCH scale.
-
- struct { // FLING velocity.
- float x;
- float y;
- float x_ordinal;
- float y_ordinal;
- } fling_velocity;
-
- int touch_id; // LONG_PRESS touch-id.
-
- // 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;
-
- 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::Rect bounding_box_;
-};
-
// An abstract type for consumers of gesture-events created by the
// gesture-recognizer.
class EVENTS_EXPORT GestureConsumer {
@@ -193,7 +28,7 @@ class EVENTS_EXPORT GestureEventHelper {
// Returns true if this helper can dispatch events to |consumer|.
virtual bool CanDispatchToConsumer(GestureConsumer* consumer) = 0;
- virtual void DispatchPostponedGestureEvent(GestureEvent* event) = 0;
+ virtual void DispatchGestureEvent(GestureEvent* event) = 0;
virtual void DispatchCancelTouchEvent(TouchEvent* event) = 0;
};
diff --git a/chromium/ui/events/gestures/gesture_util.cc b/chromium/ui/events/gestures/gesture_util.cc
deleted file mode 100644
index 5a99039453c..00000000000
--- a/chromium/ui/events/gestures/gesture_util.cc
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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_util.h"
-
-#include <stdlib.h>
-
-#include "ui/events/gestures/gesture_configuration.h"
-#include "ui/gfx/point.h"
-
-namespace ui {
-namespace gestures {
-
-bool IsInsideManhattanSquare(const gfx::Point& p1,
- const gfx::Point& p2) {
- int manhattan_distance = abs(p1.x() - p2.x()) + abs(p1.y() - p2.y());
- return manhattan_distance <
- GestureConfiguration::max_touch_move_in_pixels_for_click();
-}
-
-} // namespace gestures
-} // namespace ui
diff --git a/chromium/ui/events/gestures/gesture_util.h b/chromium/ui/events/gestures/gesture_util.h
deleted file mode 100644
index 36c1584e7b4..00000000000
--- a/chromium/ui/events/gestures/gesture_util.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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_UTIL_H_
-#define UI_EVENTS_GESTURES_GESTURE_UTIL_H_
-
-#include "ui/events/events_export.h"
-
-namespace gfx {
-class Point;
-} // namespace gfx
-
-namespace ui {
-namespace gestures {
-
-// Returns true if the distance between points |p1| and |p2| is less than a
-// threshold. This is generally used to determine if a touch point has moved
-// enough to be no longer considered a tap.
-EVENTS_EXPORT bool IsInsideManhattanSquare(const gfx::Point& p1,
- const gfx::Point& p2);
-
-} // namespace gestures
-} // namespace ui
-
-#endif // UI_EVENTS_GESTURES_GESTURE_UTIL_H_
diff --git a/chromium/ui/events/gestures/gestures.dot b/chromium/ui/events/gestures/gestures.dot
index 07883f8d274..168d02f6fff 100644
--- a/chromium/ui/events/gestures/gestures.dot
+++ b/chromium/ui/events/gestures/gestures.dot
@@ -25,11 +25,16 @@ GS_PENDING_SYNTHETIC_CLICK -> GS_NO_GESTURE [label= "C0\n R0"];
GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL [label= "M0"];
GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_TWO_FINGER_TAP [label= "D1"];
GS_PENDING_SYNTHETIC_CLICK -> GS_PENDING_PINCH [label= "D1"];
+GS_PENDING_SYNTHETIC_CLICK -> GS_SYNTHETIC_CLICK_ABORTED [label= "M0"];
GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL [label= "M0\n S0"];
GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_NO_GESTURE [label= "C0\n R0"];
GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_PENDING_TWO_FINGER_TAP [label= "D1"];
GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_PENDING_PINCH [label= "D1"];
+GS_PENDING_SYNTHETIC_CLICK_NO_SCROLL -> GS_SYNTHETIC_CLICK_ABORTED [label= "M0"];
+
+GS_SYNTHETIC_CLICK_ABORTED -> GS_NO_GESTURE [label= "C0\n R0"];
+GS_SYNTHETIC_CLICK_ABORTED -> GS_PENDING_PINCH [label= "D1"];
GS_SCROLL -> GS_SCROLL [label= "M0"];
GS_SCROLL -> GS_NO_GESTURE [label= "C0\n R0\n"];
diff --git a/chromium/ui/events/gestures/motion_event_aura.cc b/chromium/ui/events/gestures/motion_event_aura.cc
new file mode 100644
index 00000000000..2bf4e9b3ce7
--- /dev/null
+++ b/chromium/ui/events/gestures/motion_event_aura.cc
@@ -0,0 +1,255 @@
+// 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/motion_event_aura.h"
+
+#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,
+ const PointData (&active_touches)[GestureSequence::kMaxGesturePoints])
+ : pointer_count_(pointer_count),
+ last_touch_time_(last_touch_time),
+ cached_action_(cached_action),
+ cached_action_index_(cached_action_index) {
+ 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();
+
+ // TODO(tdresser): at some point we should start using both radii if they are
+ // available, but for now we use the max.
+ point_data.major_radius = std::max(touch.radius_x(), touch.radius_y());
+ if (!point_data.major_radius)
+ point_data.major_radius = GestureConfiguration::default_radius();
+ 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);
+ 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_LE(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_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].touch_id;
+}
+
+float MotionEventAura::GetX(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].x;
+}
+
+float MotionEventAura::GetY(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].y;
+}
+
+float MotionEventAura::GetRawX(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].raw_x;
+}
+
+float MotionEventAura::GetRawY(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].raw_y;
+}
+
+float MotionEventAura::GetTouchMajor(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].major_radius * 2;
+}
+
+float MotionEventAura::GetPressure(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].pressure;
+}
+
+base::TimeTicks MotionEventAura::GetEventTime() const {
+ return last_touch_time_;
+}
+
+size_t MotionEventAura::GetHistorySize() const { return 0; }
+
+base::TimeTicks MotionEventAura::GetHistoricalEventTime(
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return base::TimeTicks();
+}
+
+float MotionEventAura::GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+float MotionEventAura::GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+float MotionEventAura::GetHistoricalY(size_t pointer_index,
+ size_t historical_index) const {
+ NOTIMPLEMENTED();
+ return 0;
+}
+
+scoped_ptr<MotionEvent> MotionEventAura::Clone() const {
+ return scoped_ptr<MotionEvent>(new MotionEventAura(pointer_count_,
+ last_touch_time_,
+ cached_action_,
+ cached_action_index_,
+ active_touches_));
+}
+scoped_ptr<MotionEvent> MotionEventAura::Cancel() const {
+ return scoped_ptr<MotionEvent>(new MotionEventAura(
+ pointer_count_, last_touch_time_, ACTION_CANCEL, -1, 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),
+ major_radius(0) {
+}
+
+int MotionEventAura::GetSourceDeviceId(size_t pointer_index) const {
+ DCHECK_LE(pointer_index, pointer_count_);
+ return active_touches_[pointer_index].source_device_id;
+}
+
+void MotionEventAura::AddTouch(const TouchEvent& touch) {
+ if (pointer_count_ == static_cast<size_t>(GestureSequence::kMaxGesturePoints))
+ 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_LE(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/chromium/ui/events/gestures/motion_event_aura.h b/chromium/ui/events/gestures/motion_event_aura.h
new file mode 100644
index 00000000000..7ebf264103f
--- /dev/null
+++ b/chromium/ui/events/gestures/motion_event_aura.h
@@ -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.
+
+#ifndef UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
+#define UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
+
+#include "ui/events/gesture_detection/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/gestures/gesture_sequence.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 GetPressure(size_t pointer_index) const OVERRIDE;
+ virtual base::TimeTicks GetEventTime() const OVERRIDE;
+
+ virtual size_t GetHistorySize() const OVERRIDE;
+ virtual base::TimeTicks GetHistoricalEventTime(size_t historical_index) const
+ OVERRIDE;
+ virtual float GetHistoricalTouchMajor(size_t pointer_index,
+ size_t historical_index) const OVERRIDE;
+ virtual float GetHistoricalX(size_t pointer_index,
+ size_t historical_index) const OVERRIDE;
+ virtual float GetHistoricalY(size_t pointer_index,
+ size_t historical_index) 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 major_radius;
+ };
+
+ MotionEventAura(
+ size_t pointer_count,
+ const base::TimeTicks& last_touch_time,
+ Action cached_action,
+ int cached_action_index,
+ const PointData (&active_touches)[GestureSequence::kMaxGesturePoints]);
+
+ 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_;
+
+ // We want constant time indexing by pointer_index, and fast indexing by id.
+ // TODO(tdresser): figure out which constant to use here.
+ PointData active_touches_[GestureSequence::kMaxGesturePoints];
+
+ DISALLOW_COPY_AND_ASSIGN(MotionEventAura);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURE_DETECTION_UI_MOTION_EVENT_H_
diff --git a/chromium/ui/events/gestures/motion_event_aura_unittest.cc b/chromium/ui/events/gestures/motion_event_aura_unittest.cc
new file mode 100644
index 00000000000..c45348efeee
--- /dev/null
+++ b/chromium/ui/events/gestures/motion_event_aura_unittest.cc
@@ -0,0 +1,323 @@
+// 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.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,
+ float radius,
+ float pressure) {
+ ui::TouchEvent event(type,
+ gfx::PointF(x, y),
+ 0,
+ id,
+ base::TimeDelta::FromMilliseconds(0),
+ radius,
+ radius,
+ 0,
+ pressure);
+ event.set_root_location(gfx::PointF(raw_x, raw_y));
+ 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.
+ 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;
+ float r;
+ float p;
+
+ x = 14.4f;
+ y = 17.3f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ r = 25.7f;
+ p = 48.2f;
+ TouchEvent press0 =
+ TouchWithPosition(ET_TOUCH_PRESSED, ids[0], x, y, raw_x, raw_y, r, p);
+ 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));
+ EXPECT_FLOAT_EQ(r, event.GetTouchMajor(0) / 2);
+ EXPECT_FLOAT_EQ(p, event.GetPressure(0));
+
+ x = 17.8f;
+ y = 12.1f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ r = 21.2f;
+ p = 18.4f;
+ TouchEvent press1 =
+ TouchWithPosition(ET_TOUCH_PRESSED, ids[1], x, y, raw_x, raw_y, r, p);
+ 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));
+ EXPECT_FLOAT_EQ(r, event.GetTouchMajor(1) / 2);
+ EXPECT_FLOAT_EQ(p, event.GetPressure(1));
+
+ // Test cloning of pointer location information.
+ scoped_ptr<MotionEvent> clone = event.Clone();
+ EXPECT_EQ(2U, clone->GetPointerCount());
+ EXPECT_FLOAT_EQ(x, clone->GetX(1));
+ EXPECT_FLOAT_EQ(y, clone->GetY(1));
+ EXPECT_FLOAT_EQ(raw_x, event.GetRawX(1));
+ EXPECT_FLOAT_EQ(raw_y, event.GetRawY(1));
+ EXPECT_FLOAT_EQ(r, clone->GetTouchMajor(1) / 2);
+ EXPECT_FLOAT_EQ(p, clone->GetPressure(1));
+
+ x = 27.9f;
+ y = 22.3f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ r = 7.6f;
+ p = 82.1f;
+ TouchEvent move1 =
+ TouchWithPosition(ET_TOUCH_MOVED, ids[1], x, y, raw_x, raw_y, r, p);
+ 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));
+ EXPECT_FLOAT_EQ(r, event.GetTouchMajor(1) / 2);
+ EXPECT_FLOAT_EQ(p, event.GetPressure(1));
+
+ x = 34.6f;
+ y = 23.8f;
+ raw_x = x + kRawOffsetX;
+ raw_y = y + kRawOffsetY;
+ r = 12.9f;
+ p = 14.2f;
+ TouchEvent move0 =
+ TouchWithPosition(ET_TOUCH_MOVED, ids[0], x, y, raw_x, raw_y, r, p);
+ 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));
+ EXPECT_FLOAT_EQ(r, event.GetTouchMajor(0) / 2);
+ EXPECT_FLOAT_EQ(p, event.GetPressure(0));
+}
+
+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());
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gestures/unified_gesture_detector_enabled.cc b/chromium/ui/events/gestures/unified_gesture_detector_enabled.cc
new file mode 100644
index 00000000000..cf221ff9d03
--- /dev/null
+++ b/chromium/ui/events/gestures/unified_gesture_detector_enabled.cc
@@ -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.
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "ui/events/event_switches.h"
+#include "ui/events/gestures/unified_gesture_detector_enabled.h"
+
+namespace ui {
+
+bool IsUnifiedGestureDetectorEnabled() {
+ const bool kUseUnifiedGestureDetectorByDefault = true;
+
+ const CommandLine& command_line = *CommandLine::ForCurrentProcess();
+ const std::string unified_gd_enabled_switch =
+ command_line.HasSwitch(switches::kUnifiedGestureDetector) ?
+ command_line.GetSwitchValueASCII(switches::kUnifiedGestureDetector) :
+ switches::kUnifiedGestureDetectorAuto;
+
+ if (unified_gd_enabled_switch.empty() ||
+ unified_gd_enabled_switch == switches::kUnifiedGestureDetectorEnabled) {
+ return true;
+ }
+
+ if (unified_gd_enabled_switch == switches::kUnifiedGestureDetectorDisabled)
+ return false;
+
+ if (unified_gd_enabled_switch == switches::kUnifiedGestureDetectorAuto)
+ return kUseUnifiedGestureDetectorByDefault;
+
+ LOG(ERROR) << "Invalid --unified-gesture-detector option: "
+ << unified_gd_enabled_switch;
+ return false;
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/gestures/unified_gesture_detector_enabled.h b/chromium/ui/events/gestures/unified_gesture_detector_enabled.h
new file mode 100644
index 00000000000..18fdc26d8ac
--- /dev/null
+++ b/chromium/ui/events/gestures/unified_gesture_detector_enabled.h
@@ -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.
+
+#ifndef UI_EVENTS_GESTURES_UNIFIED_GESTURE_DETECTOR_ENABLED_H_
+#define UI_EVENTS_GESTURES_UNIFIED_GESTURE_DETECTOR_ENABLED_H_
+
+#include "ui/events/events_export.h"
+
+namespace ui {
+
+// Returns true iff the unified gesture detector is enabled for Aura.
+EVENTS_EXPORT bool IsUnifiedGestureDetectorEnabled();
+
+} // namespace ui
+
+#endif // UI_EVENTS_GESTURES_UNIFIED_GESTURE_DETECTOR_ENABLED_H_
diff --git a/chromium/ui/events/gestures/velocity_calculator.cc b/chromium/ui/events/gestures/velocity_calculator.cc
index 5380d5126fa..37ec671b17f 100644
--- a/chromium/ui/events/gestures/velocity_calculator.cc
+++ b/chromium/ui/events/gestures/velocity_calculator.cc
@@ -30,7 +30,7 @@ float VelocityCalculator::YVelocity() {
return y_velocity_;
}
-void VelocityCalculator::PointSeen(int x, int y, int64 time) {
+void VelocityCalculator::PointSeen(float x, float y, int64 time) {
buffer_[index_].x = x;
buffer_[index_].y = y;
buffer_[index_].time = time;
diff --git a/chromium/ui/events/gestures/velocity_calculator.h b/chromium/ui/events/gestures/velocity_calculator.h
index 1690438ae32..3678514f271 100644
--- a/chromium/ui/events/gestures/velocity_calculator.h
+++ b/chromium/ui/events/gestures/velocity_calculator.h
@@ -17,7 +17,7 @@ class EVENTS_EXPORT VelocityCalculator {
public:
explicit VelocityCalculator(int bufferSize);
~VelocityCalculator();
- void PointSeen(int x, int y, int64 time);
+ void PointSeen(float x, float y, int64 time);
float XVelocity();
float YVelocity();
float VelocitySquared();
@@ -25,8 +25,8 @@ class EVENTS_EXPORT VelocityCalculator {
private:
struct Point {
- int x;
- int y;
+ float x;
+ float y;
int64 time;
};
diff --git a/chromium/ui/events/gestures/velocity_calculator_unittest.cc b/chromium/ui/events/gestures/velocity_calculator_unittest.cc
index 3352acd6722..4e892170d04 100644
--- a/chromium/ui/events/gestures/velocity_calculator_unittest.cc
+++ b/chromium/ui/events/gestures/velocity_calculator_unittest.cc
@@ -74,7 +74,7 @@ TEST(VelocityCalculatorTest, IsAccurateWithLargeTimes) {
EXPECT_GT(velocity_calculator.YVelocity(), -1270000);
EXPECT_LT(velocity_calculator.YVelocity(), -1240000);
- start_time = GG_LONGLONG(1223372036800000000);
+ start_time = 1223372036800000000LL;
velocity_calculator.PointSeen(9, -11, start_time);
velocity_calculator.PointSeen(21, -19, start_time + 8);
velocity_calculator.PointSeen(30, -32, start_time + 16);
diff --git a/chromium/ui/events/ipc/BUILD.gn b/chromium/ui/events/ipc/BUILD.gn
new file mode 100644
index 00000000000..82ac834cf3d
--- /dev/null
+++ b/chromium/ui/events/ipc/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+component("events_ipc") {
+ sources = [
+ "latency_info_param_traits.cc",
+ "latency_info_param_traits.h",
+ ]
+
+ defines = [ "EVENTS_IMPLEMENTATION" ]
+
+ deps = [ "//ipc" ]
+}
+
diff --git a/chromium/ui/events/ipc/OWNERS b/chromium/ui/events/ipc/OWNERS
new file mode 100644
index 00000000000..2d12f8e4d21
--- /dev/null
+++ b/chromium/ui/events/ipc/OWNERS
@@ -0,0 +1,13 @@
+set noparent
+cevans@chromium.org
+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/chromium/ui/events/ipc/events_ipc.gyp b/chromium/ui/events/ipc/events_ipc.gyp
new file mode 100644
index 00000000000..dd6433d33a6
--- /dev/null
+++ b/chromium/ui/events/ipc/events_ipc.gyp
@@ -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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ '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/chromium/ui/events/ipc/latency_info_param_traits.cc b/chromium/ui/events/ipc/latency_info_param_traits.cc
new file mode 100644
index 00000000000..0b36c3121aa
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ipc/latency_info_param_traits.h b/chromium/ui/events/ipc/latency_info_param_traits.h
new file mode 100644
index 00000000000..0206d7d52e6
--- /dev/null
+++ b/chromium/ui/events/ipc/latency_info_param_traits.h
@@ -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.
+
+#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)
+ IPC_STRUCT_TRAITS_MEMBER(latency_components)
+ IPC_STRUCT_TRAITS_MEMBER(trace_id)
+ IPC_STRUCT_TRAITS_MEMBER(terminated)
+IPC_STRUCT_TRAITS_END()
+
+#endif // UI_EVENTS_IPC_LATENCY_INFO_PARAM_TRAITS_H_
diff --git a/chromium/ui/events/keycodes/DEPS b/chromium/ui/events/keycodes/DEPS
new file mode 100644
index 00000000000..a2ede37fc05
--- /dev/null
+++ b/chromium/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/chromium/ui/events/keycodes/dom4/DEPS b/chromium/ui/events/keycodes/dom4/DEPS
new file mode 100644
index 00000000000..b5489564447
--- /dev/null
+++ b/chromium/ui/events/keycodes/dom4/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+ui/events/keycodes/dom4",
+]
diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc b/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc
deleted file mode 100644
index 9a396534e20..00000000000
--- a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// 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.
-
-/*
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
- * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
- * Copyright (C) 2007 Holger Hans Peter Freyther
- * Copyright (C) 2008 Collabora, Ltd. 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.
- */
-
-// WindowsKeyCodeForGdkKeyCode is copied from platform/gtk/KeyEventGtk.cpp
-
-#include "ui/events/keycodes/keyboard_code_conversion_gtk.h"
-
-#include <gdk/gdk.h>
-#include <gdk/gdkkeysyms.h>
-#include <X11/keysym.h>
-
-#include "base/basictypes.h"
-#include "build/build_config.h"
-#include "ui/events/keycodes/keyboard_code_conversion_x.h"
-#include "ui/events/keycodes/keyboard_codes_posix.h"
-
-namespace ui {
-
-KeyboardCode WindowsKeyCodeForGdkKeyCode(int keycode) {
- // Gdk key codes (e.g. GDK_BackSpace) and X keysyms (e.g. XK_BackSpace) share
- // the same values.
- return KeyboardCodeFromXKeysym(keycode);
-}
-
-int GdkKeyCodeForWindowsKeyCode(KeyboardCode keycode, bool shift) {
- // Gdk key codes and X keysyms share the same values.
- return XKeysymForWindowsKeyCode(keycode, shift);
-}
-
-// Just in case, test whether Gdk key codes match X ones.
-COMPILE_ASSERT(GDK_KP_0 == XK_KP_0, keycode_check);
-COMPILE_ASSERT(GDK_A == XK_A, keycode_check);
-COMPILE_ASSERT(GDK_Escape == XK_Escape, keycode_check);
-COMPILE_ASSERT(GDK_F1 == XK_F1, keycode_check);
-COMPILE_ASSERT(GDK_Kanji == XK_Kanji, keycode_check);
-COMPILE_ASSERT(GDK_Page_Up == XK_Page_Up, keycode_check);
-COMPILE_ASSERT(GDK_Tab == XK_Tab, keycode_check);
-COMPILE_ASSERT(GDK_a == XK_a, keycode_check);
-COMPILE_ASSERT(GDK_space == XK_space, keycode_check);
-
-int GdkNativeKeyCodeForWindowsKeyCode(KeyboardCode keycode, bool shift) {
- int keyval = GdkKeyCodeForWindowsKeyCode(keycode, shift);
- GdkKeymapKey* keys;
- gint n_keys;
-
- int native_keycode = 0;
- if (keyval && gdk_keymap_get_entries_for_keyval(0, keyval, &keys, &n_keys)) {
- native_keycode = keys[0].keycode;
- g_free(keys);
- }
-
- return native_keycode;
-}
-
-} // namespace ui
diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h b/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h
deleted file mode 100644
index 6258f4116c1..00000000000
--- a/chromium/ui/events/keycodes/keyboard_code_conversion_gtk.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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.
-
-/*
- * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
- * Copyright (C) 2006 Michael Emmel mike.emmel@gmail.com
- * Copyright (C) 2007 Holger Hans Peter Freyther
- * Copyright (C) 2008 Collabora, Ltd. 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.
- */
-
-// WindowsKeyCodeForGdkKeyCode is copied from platform/gtk/KeyEventGtk.cpp
-
-#ifndef UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_GTK_H_
-#define UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_GTK_H_
-
-#include "ui/events/events_base_export.h"
-#include "ui/events/keycodes/keyboard_codes_posix.h"
-
-typedef struct _GdkEventKey GdkEventKey;
-
-namespace ui {
-
-EVENTS_BASE_EXPORT KeyboardCode WindowsKeyCodeForGdkKeyCode(int keycode);
-
-EVENTS_BASE_EXPORT int GdkKeyCodeForWindowsKeyCode(KeyboardCode keycode,
- bool shift);
-
-// For WebKit DRT testing: simulate the native keycode for the given
-// input |keycode|. Return the native keycode.
-EVENTS_BASE_EXPORT int GdkNativeKeyCodeForWindowsKeyCode(KeyboardCode keycode,
- bool shift);
-
-} // namespace ui
-
-#endif // UI_EVENTS_KEYCODES_KEYBOARD_CODE_CONVERSION_GTK_H_
diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h
index 719c922d139..fee39815d42 100644
--- a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h
+++ b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.h
@@ -16,8 +16,8 @@ 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 modifiers mask such
-// as NSControlKeyMask, NSShiftKeyMask, etc.
+// 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
diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm
index fdda790be1f..e354e58883c 100644
--- a/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm
+++ b/chromium/ui/events/keycodes/keyboard_code_conversion_mac.mm
@@ -521,18 +521,6 @@ int MacKeyCodeForWindowsKeyCode(KeyboardCode keycode,
}
}
- // Control characters.
- if (flags & NSControlKeyMask) {
- if (keycode >= VKEY_A && keycode <= VKEY_Z)
- *character = 1 + keycode - VKEY_A;
- else if (macKeycode == kVK_ANSI_LeftBracket)
- *character = 27;
- else if (macKeycode == kVK_ANSI_Backslash)
- *character = 28;
- else if (macKeycode == kVK_ANSI_RightBracket)
- *character = 29;
- }
-
// TODO(suzhe): Support characters for Option key bindings.
return macKeycode;
}
diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc b/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc
index 821f8c47013..4f277ecff27 100644
--- a/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc
+++ b/chromium/ui/events/keycodes/keyboard_code_conversion_x.cc
@@ -4,6 +4,8 @@
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
+#include <algorithm>
+
#define XK_3270 // for XK_3270_BackTab
#include <X11/keysym.h>
#include <X11/Xlib.h>
@@ -13,23 +15,529 @@
#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"
+#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(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;
+ XKeyEvent xkey = xev->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.
- KeySym keysym;
- XLookupString(&xev->xkey, NULL, 0, &keysym, NULL);
- KeyboardCode keycode = KeyboardCodeFromXKeysym(keysym);
- if (keycode == VKEY_UNKNOWN) {
- keysym = DefaultXKeysymFromHardwareKeycode(xev->xkey.keycode);
- keycode = KeyboardCodeFromXKeysym(keysym);
+ 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;
}
@@ -99,117 +607,6 @@ KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym) {
return VKEY_NONCONVERT;
case XK_Zenkaku_Hankaku:
return VKEY_DBE_DBCSCHAR;
- case XK_A:
- case XK_a:
- return VKEY_A;
- case XK_B:
- case XK_b:
- return VKEY_B;
- case XK_C:
- case XK_c:
- return VKEY_C;
- case XK_D:
- case XK_d:
- return VKEY_D;
- case XK_E:
- case XK_e:
- return VKEY_E;
- case XK_F:
- case XK_f:
- return VKEY_F;
- case XK_G:
- case XK_g:
- return VKEY_G;
- case XK_H:
- case XK_h:
- return VKEY_H;
- case XK_I:
- case XK_i:
- return VKEY_I;
- case XK_J:
- case XK_j:
- return VKEY_J;
- case XK_K:
- case XK_k:
- return VKEY_K;
- case XK_L:
- case XK_l:
- return VKEY_L;
- case XK_M:
- case XK_m:
- return VKEY_M;
- case XK_N:
- case XK_n:
- return VKEY_N;
- case XK_O:
- case XK_o:
- return VKEY_O;
- case XK_P:
- case XK_p:
- return VKEY_P;
- case XK_Q:
- case XK_q:
- return VKEY_Q;
- case XK_R:
- case XK_r:
- return VKEY_R;
- case XK_S:
- case XK_s:
- return VKEY_S;
- case XK_T:
- case XK_t:
- return VKEY_T;
- case XK_U:
- case XK_u:
- return VKEY_U;
- case XK_V:
- case XK_v:
- return VKEY_V;
- case XK_W:
- case XK_w:
- return VKEY_W;
- case XK_X:
- case XK_x:
- return VKEY_X;
- case XK_Y:
- case XK_y:
- return VKEY_Y;
- case XK_Z:
- case XK_z:
- return VKEY_Z;
-
- case XK_0:
- case XK_1:
- case XK_2:
- case XK_3:
- case XK_4:
- case XK_5:
- case XK_6:
- case XK_7:
- case XK_8:
- case XK_9:
- return static_cast<KeyboardCode>(VKEY_0 + (keysym - XK_0));
-
- case XK_parenright:
- return VKEY_0;
- case XK_exclam:
- return VKEY_1;
- case XK_at:
- return VKEY_2;
- case XK_numbersign:
- return VKEY_3;
- case XK_dollar:
- return VKEY_4;
- case XK_percent:
- return VKEY_5;
- case XK_asciicircum:
- return VKEY_6;
- case XK_ampersand:
- return VKEY_7;
- case XK_asterisk:
- return VKEY_8;
- case XK_parenleft:
- return VKEY_9;
case XK_KP_0:
case XK_KP_1:
@@ -371,20 +768,6 @@ KeyboardCode KeyboardCodeFromXKeysym(unsigned int keysym) {
case XF86XK_Launch9:
return VKEY_F18;
-#if defined(TOOLKIT_GTK)
- case XF86XK_Refresh:
- case XF86XK_History:
- case XF86XK_OpenURL:
- case XF86XK_AddFavorite:
- case XF86XK_Go:
- case XF86XK_ZoomIn:
- case XF86XK_ZoomOut:
- // ui::AcceleratorGtk tries to convert the XF86XK_ keysyms on Chrome
- // startup. It's safe to return VKEY_UNKNOWN here since ui::AcceleratorGtk
- // also checks a Gdk keysym. http://crbug.com/109843
- return VKEY_UNKNOWN;
-#endif
-
// For supporting multimedia buttons on a USB keyboard.
case XF86XK_Back:
return VKEY_BROWSER_BACK;
@@ -450,96 +833,178 @@ uint16 GetCharacterFromXEvent(XEvent* xev) {
int bytes_written = XLookupString(&xev->xkey, buf, 6, NULL, NULL);
DCHECK_LE(bytes_written, 6);
- base::string16 result;
- return (bytes_written > 0 && UTF8ToUTF16(buf, bytes_written, &result) &&
- result.length() == 1) ? result[0] : 0;
+ if (bytes_written <= 0)
+ return 0;
+ const base::string16& result = base::WideToUTF16(
+ base::SysNativeMBToWide(base::StringPiece(buf, bytes_written)));
+ return result.length() == 1 ? result[0] : 0;
}
-unsigned int DefaultXKeysymFromHardwareKeycode(unsigned int hardware_code) {
- static const unsigned int kHardwareKeycodeMap[] = {
- 0, // 0x00:
- 0, // 0x01:
- 0, // 0x02:
- 0, // 0x03:
- 0, // 0x04:
- 0, // 0x05:
- 0, // 0x06:
- 0, // 0x07:
- 0, // 0x08:
- XK_Escape, // 0x09: XK_Escape
- XK_1, // 0x0A: XK_1
- XK_2, // 0x0B: XK_2
- XK_3, // 0x0C: XK_3
- XK_4, // 0x0D: XK_4
- XK_5, // 0x0E: XK_5
- XK_6, // 0x0F: XK_6
- XK_7, // 0x10: XK_7
- XK_8, // 0x11: XK_8
- XK_9, // 0x12: XK_9
- XK_0, // 0x13: XK_0
- XK_minus, // 0x14: XK_minus
- XK_equal, // 0x15: XK_equal
- XK_BackSpace, // 0x16: XK_BackSpace
- XK_Tab, // 0x17: XK_Tab
- XK_q, // 0x18: XK_q
- XK_w, // 0x19: XK_w
- XK_e, // 0x1A: XK_e
- XK_r, // 0x1B: XK_r
- XK_t, // 0x1C: XK_t
- XK_y, // 0x1D: XK_y
- XK_u, // 0x1E: XK_u
- XK_i, // 0x1F: XK_i
- XK_o, // 0x20: XK_o
- XK_p, // 0x21: XK_p
- XK_bracketleft, // 0x22: XK_bracketleft
- XK_bracketright, // 0x23: XK_bracketright
- XK_Return, // 0x24: XK_Return
- XK_Control_L, // 0x25: XK_Control_L
- XK_a, // 0x26: XK_a
- XK_s, // 0x27: XK_s
- XK_d, // 0x28: XK_d
- XK_f, // 0x29: XK_f
- XK_g, // 0x2A: XK_g
- XK_h, // 0x2B: XK_h
- XK_j, // 0x2C: XK_j
- XK_k, // 0x2D: XK_k
- XK_l, // 0x2E: XK_l
- XK_semicolon, // 0x2F: XK_semicolon
- XK_apostrophe, // 0x30: XK_apostrophe
- XK_grave, // 0x31: XK_grave
- XK_Shift_L, // 0x32: XK_Shift_L
- XK_backslash, // 0x33: XK_backslash
- XK_z, // 0x34: XK_z
- XK_x, // 0x35: XK_x
- XK_c, // 0x36: XK_c
- XK_v, // 0x37: XK_v
- XK_b, // 0x38: XK_b
- XK_n, // 0x39: XK_n
- XK_m, // 0x3A: XK_m
- XK_comma, // 0x3B: XK_comma
- XK_period, // 0x3C: XK_period
- XK_slash, // 0x3D: XK_slash
- XK_Shift_R, // 0x3E: XK_Shift_R
- 0, // 0x3F: XK_KP_Multiply
- XK_Alt_L, // 0x40: XK_Alt_L
- XK_space, // 0x41: XK_space
- XK_Caps_Lock, // 0x42: XK_Caps_Lock
- XK_F1, // 0x43: XK_F1
- XK_F2, // 0x44: XK_F2
- XK_F3, // 0x45: XK_F3
- XK_F4, // 0x46: XK_F4
- XK_F5, // 0x47: XK_F5
- XK_F6, // 0x48: XK_F6
- XK_F7, // 0x49: XK_F7
- XK_F8, // 0x4A: XK_F8
- XK_F9, // 0x4B: XK_F9
- XK_F10, // 0x4C: XK_F10
- XK_Num_Lock, // 0x4D: XK_Num_Lock
- XK_Scroll_Lock, // 0x4E: XK_Scroll_Lock
+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
};
- return hardware_code < arraysize(kHardwareKeycodeMap) ?
- kHardwareKeycodeMap[hardware_code] : 0;
+ 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.
diff --git a/chromium/ui/events/keycodes/keyboard_code_conversion_x.h b/chromium/ui/events/keycodes/keyboard_code_conversion_x.h
index b5d8fe87c37..f9186ca0c88 100644
--- a/chromium/ui/events/keycodes/keyboard_code_conversion_x.h
+++ b/chromium/ui/events/keycodes/keyboard_code_conversion_x.h
@@ -26,8 +26,9 @@ EVENTS_BASE_EXPORT uint16 GetCharacterFromXEvent(XEvent* xev);
EVENTS_BASE_EXPORT int XKeysymForWindowsKeyCode(KeyboardCode keycode,
bool shift);
-// Converts an X keycode into an X KeySym.
-unsigned int DefaultXKeysymFromHardwareKeycode(unsigned int keycode);
+// Converts an X keycode into ui::KeyboardCode.
+EVENTS_BASE_EXPORT KeyboardCode
+ DefaultKeyboardCodeFromHardwareKeycode(unsigned int hardware_code);
} // namespace ui
diff --git a/chromium/ui/events/latency_info.cc b/chromium/ui/events/latency_info.cc
index a3a11828db0..3fedd67e736 100644
--- a/chromium/ui/events/latency_info.cc
+++ b/chromium/ui/events/latency_info.cc
@@ -11,10 +11,14 @@
#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_SCROLL_UPDATE_RWH_COMPONENT);
CASE_TYPE(INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT);
CASE_TYPE(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT);
@@ -29,6 +33,7 @@ const char* GetComponentName(ui::LatencyComponentType type) {
CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT);
CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT);
CASE_TYPE(LATENCY_INFO_LIST_TERMINATED_OVERFLOW_COMPONENT);
+ CASE_TYPE(INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT);
default:
DLOG(WARNING) << "Unhandled LatencyComponentType.\n";
break;
@@ -46,6 +51,7 @@ bool IsTerminalComponent(ui::LatencyComponentType type) {
case ui::INPUT_EVENT_LATENCY_TERMINATED_COMMIT_FAILED_COMPONENT:
case ui::INPUT_EVENT_LATENCY_TERMINATED_SWAP_FAILED_COMPONENT:
case ui::LATENCY_INFO_LIST_TERMINATED_OVERFLOW_COMPONENT:
+ case ui::INPUT_EVENT_LATENCY_TERMINATED_PLUGIN_COMPONENT:
return true;
default:
return false;
@@ -53,7 +59,8 @@ bool IsTerminalComponent(ui::LatencyComponentType type) {
}
bool IsBeginComponent(ui::LatencyComponentType type) {
- return (type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT);
+ return (type == ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT ||
+ type == ui::INPUT_EVENT_LATENCY_BEGIN_PLUGIN_COMPONENT);
}
// This class is for converting latency info to trace buffer friendly format.
@@ -119,16 +126,28 @@ LatencyInfo::LatencyInfo() : trace_id(-1), terminated(false) {
LatencyInfo::~LatencyInfo() {
}
-void LatencyInfo::MergeWith(const LatencyInfo& other) {
+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;
+ }
+ 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) {
- AddLatencyNumberWithTimestamp(it->first.first,
- it->first.second,
- it->second.sequence_number,
- it->second.event_time,
- it->second.event_count,
- false);
+ if (it->first.first == type) {
+ AddLatencyNumberWithTimestamp(it->first.first,
+ it->first.second,
+ it->second.sequence_number,
+ it->second.event_time,
+ it->second.event_count);
+ }
}
}
@@ -141,8 +160,7 @@ void LatencyInfo::AddNewLatencyFrom(const LatencyInfo& other) {
it->first.second,
it->second.sequence_number,
it->second.event_time,
- it->second.event_count,
- false);
+ it->second.event_count);
}
}
}
@@ -151,22 +169,23 @@ void LatencyInfo::AddLatencyNumber(LatencyComponentType component,
int64 id,
int64 component_sequence_number) {
AddLatencyNumberWithTimestamp(component, id, component_sequence_number,
- base::TimeTicks::HighResNow(), 1, true);
+ base::TimeTicks::HighResNow(), 1);
}
void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component,
int64 id,
int64 component_sequence_number,
base::TimeTicks time,
- uint32 event_count,
- bool dump_to_trace) {
- if (dump_to_trace && IsBeginComponent(component)) {
+ uint32 event_count) {
+ if (IsBeginComponent(component)) {
// Should only ever add begin component once.
CHECK_EQ(-1, trace_id);
trace_id = component_sequence_number;
TRACE_EVENT_ASYNC_BEGIN0("benchmark",
"InputLatency",
TRACE_ID_DONT_MANGLE(trace_id));
+ TRACE_EVENT_FLOW_BEGIN0(
+ "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id));
}
LatencyMap::key_type key = std::make_pair(component, id);
@@ -188,7 +207,7 @@ void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component,
}
}
- if (dump_to_trace && IsTerminalComponent(component) && trace_id != -1) {
+ if (IsTerminalComponent(component) && trace_id != -1) {
// Should only ever add terminal component once.
CHECK(!terminated);
terminated = true;
@@ -196,6 +215,8 @@ void LatencyInfo::AddLatencyNumberWithTimestamp(LatencyComponentType component,
"InputLatency",
TRACE_ID_DONT_MANGLE(trace_id),
"data", AsTraceableData(*this));
+ TRACE_EVENT_FLOW_END0(
+ "input", "LatencyInfo.Flow", TRACE_ID_DONT_MANGLE(trace_id));
}
}
diff --git a/chromium/ui/events/latency_info.h b/chromium/ui/events/latency_info.h
index 3e50cc4f80c..619b0462f71 100644
--- a/chromium/ui/events/latency_info.h
+++ b/chromium/ui/events/latency_info.h
@@ -5,10 +5,11 @@
#ifndef UI_EVENTS_LATENCY_INFO_H_
#define UI_EVENTS_LATENCY_INFO_H_
-#include <map>
#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"
@@ -19,6 +20,8 @@ enum LatencyComponentType {
// 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,
// ---------------------------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
@@ -64,6 +67,10 @@ enum LatencyComponentType {
// This component indicates that the cached LatencyInfo number exceeds the
// maximal allowed size.
LATENCY_INFO_LIST_TERMINATED_OVERFLOW_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 {
@@ -78,17 +85,31 @@ struct EVENTS_BASE_EXPORT LatencyInfo {
uint32 event_count;
};
+ // Empirically determined constant based on a typical scroll sequence.
+ enum { kTypicalMaxComponentsPerLatencyInfo = 6 };
+
// Map a Latency Component (with a component-specific int64 id) to a
// component info.
- typedef std::map<std::pair<LatencyComponentType, int64>, LatencyComponent>
- LatencyMap;
+ typedef base::SmallMap<
+ std::map<std::pair<LatencyComponentType, int64>, LatencyComponent>,
+ kTypicalMaxComponentsPerLatencyInfo> LatencyMap;
LatencyInfo();
~LatencyInfo();
- // Merges the contents of another LatencyInfo into this one.
- void MergeWith(const LatencyInfo& other);
+ // 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);
@@ -101,13 +122,11 @@ struct EVENTS_BASE_EXPORT LatencyInfo {
// Modifies the current sequence number and adds a certain number of events
// for a specific component.
- // TODO(miletus): Remove the |dump_to_trace| once we remove MergeWith().
void AddLatencyNumberWithTimestamp(LatencyComponentType component,
int64 id,
int64 component_sequence_number,
base::TimeTicks time,
- uint32 event_count,
- bool dump_to_trace);
+ 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
diff --git a/chromium/ui/events/latency_info_nacl.gyp b/chromium/ui/events/latency_info_nacl.gyp
new file mode 100644
index 00000000000..e42dbf87ee4
--- /dev/null
+++ b/chromium/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/chromium/ui/events/latency_info_unittest.cc b/chromium/ui/events/latency_info_unittest.cc
index ec39a0dbcba..2f9ad0562be 100644
--- a/chromium/ui/events/latency_info_unittest.cc
+++ b/chromium/ui/events/latency_info_unittest.cc
@@ -14,14 +14,12 @@ TEST(LatencyInfoTest, AddTwoSeparateEvent) {
0,
1,
base::TimeTicks::FromInternalValue(100),
- 1,
- true);
+ 1);
info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
1,
5,
base::TimeTicks::FromInternalValue(1000),
- 2,
- true);
+ 2);
EXPECT_EQ(info.latency_components.size(), 2u);
LatencyInfo::LatencyComponent component;
@@ -47,14 +45,12 @@ TEST(LatencyInfoTest, AddTwoSameEvent) {
0,
30,
base::TimeTicks::FromInternalValue(100),
- 2,
- true);
+ 2);
info.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
0,
13,
base::TimeTicks::FromInternalValue(200),
- 3,
- true);
+ 3);
EXPECT_EQ(info.latency_components.size(), 1u);
LatencyInfo::LatencyComponent component;
@@ -69,79 +65,13 @@ TEST(LatencyInfoTest, AddTwoSameEvent) {
EXPECT_EQ(component.event_time.ToInternalValue(), (100 * 2 + 200 * 3) / 5);
}
-TEST(LatencyInfoTest, MergeTwoSeparateEvent) {
- LatencyInfo info1;
- LatencyInfo info2;
- info1.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
- 0,
- 1,
- base::TimeTicks::FromInternalValue(100),
- 1,
- true);
- info2.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
- 1,
- 5,
- base::TimeTicks::FromInternalValue(1000),
- 2,
- true);
- info1.MergeWith(info2);
-
- EXPECT_EQ(info1.latency_components.size(), 2u);
- LatencyInfo::LatencyComponent component;
- EXPECT_FALSE(
- info1.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component));
- EXPECT_FALSE(info1.FindLatency(
- INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 1, &component));
- EXPECT_TRUE(info1.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(
- info1.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, MergeTwoSameEvent) {
- LatencyInfo info1;
- LatencyInfo info2;
- info1.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
- 0,
- 30,
- base::TimeTicks::FromInternalValue(100),
- 2,
- true);
- info2.AddLatencyNumberWithTimestamp(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
- 0,
- 13,
- base::TimeTicks::FromInternalValue(200),
- 3,
- true);
- info1.MergeWith(info2);
-
- EXPECT_EQ(info1.latency_components.size(), 1u);
- LatencyInfo::LatencyComponent component;
- EXPECT_FALSE(
- info1.FindLatency(INPUT_EVENT_LATENCY_UI_COMPONENT, 0, &component));
- EXPECT_FALSE(
- info1.FindLatency(INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 1, &component));
- EXPECT_TRUE(
- info1.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,
- true);
+ 2);
EXPECT_EQ(info.latency_components.size(), 1u);
info.Clear();
diff --git a/chromium/ui/events/linux/text_edit_command_auralinux.cc b/chromium/ui/events/linux/text_edit_command_auralinux.cc
new file mode 100644
index 00000000000..4429225f48c
--- /dev/null
+++ b/chromium/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/chromium/ui/events/linux/text_edit_command_auralinux.h b/chromium/ui/events/linux/text_edit_command_auralinux.h
new file mode 100644
index 00000000000..82d3af24306
--- /dev/null
+++ b/chromium/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/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc b/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.cc
new file mode 100644
index 00000000000..e6a941bcda4
--- /dev/null
+++ b/chromium/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/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h b/chromium/ui/events/linux/text_edit_key_bindings_delegate_auralinux.h
new file mode 100644
index 00000000000..d2916260380
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/BUILD.gn b/chromium/ui/events/ozone/BUILD.gn
new file mode 100644
index 00000000000..a5054b7eb7b
--- /dev/null
+++ b/chromium/ui/events/ozone/BUILD.gn
@@ -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.
+
+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",
+ "event_factory_ozone.cc",
+ "event_factory_ozone.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/chromium/ui/events/ozone/DEPS b/chromium/ui/events/ozone/DEPS
new file mode 100644
index 00000000000..3f271147d36
--- /dev/null
+++ b/chromium/ui/events/ozone/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+ "+device/udev_linux",
+ "+ui/ozone/public",
+]
diff --git a/chromium/ui/events/ozone/OWNERS b/chromium/ui/events/ozone/OWNERS
index 77f21b5cae7..479c4d806cc 100644
--- a/chromium/ui/events/ozone/OWNERS
+++ b/chromium/ui/events/ozone/OWNERS
@@ -1 +1,2 @@
rjkroege@chromium.org
+spang@chromium.org
diff --git a/chromium/ui/events/ozone/device/device_event.cc b/chromium/ui/events/ozone/device/device_event.cc
new file mode 100644
index 00000000000..e20079a79cf
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/device_event.h b/chromium/ui/events/ozone/device/device_event.h
new file mode 100644
index 00000000000..a24394c75af
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/device_event_observer.h b/chromium/ui/events/ozone/device/device_event_observer.h
new file mode 100644
index 00000000000..7ad2f4966c1
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/device_manager.cc b/chromium/ui/events/ozone/device/device_manager.cc
new file mode 100644
index 00000000000..bbd5d800fa5
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/device_manager.h b/chromium/ui/events/ozone/device/device_manager.h
new file mode 100644
index 00000000000..0551e7ecf8b
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/device_manager_manual.cc b/chromium/ui/events/ozone/device/device_manager_manual.cc
new file mode 100644
index 00000000000..c86ffccf62b
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/device_manager_manual.h b/chromium/ui/events/ozone/device/device_manager_manual.h
new file mode 100644
index 00000000000..9d1cf61d9d7
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/udev/device_manager_udev.cc b/chromium/ui/events/ozone/device/udev/device_manager_udev.cc
new file mode 100644
index 00000000000..7f86bee4f84
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/device/udev/device_manager_udev.h b/chromium/ui/events/ozone/device/udev/device_manager_udev.h
new file mode 100644
index 00000000000..8a7537abebe
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/evdev/cursor_delegate_evdev.h b/chromium/ui/events/ozone/evdev/cursor_delegate_evdev.h
new file mode 100644
index 00000000000..7ff01befd9e
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/cursor_delegate_evdev.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_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;
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_CURSOR_DELEGATE_EVDEV_H_
diff --git a/chromium/ui/events/ozone/evdev/event_converter_evdev.cc b/chromium/ui/events/ozone/evdev/event_converter_evdev.cc
new file mode 100644
index 00000000000..5b1e9a58981
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/event_converter_evdev.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/evdev/event_converter_evdev.h"
+
+#include "ui/events/event.h"
+#include "ui/ozone/public/event_factory_ozone.h"
+
+namespace ui {
+
+EventConverterEvdev::EventConverterEvdev() {}
+
+EventConverterEvdev::EventConverterEvdev(const EventDispatchCallback& callback)
+ : dispatch_callback_(callback) {}
+
+EventConverterEvdev::~EventConverterEvdev() {}
+
+void EventConverterEvdev::DispatchEventToCallback(ui::Event* event) {
+ dispatch_callback_.Run(event);
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/event_converter_evdev.h b/chromium/ui/events/ozone/evdev/event_converter_evdev.h
new file mode 100644
index 00000000000..ebc36fcc5a2
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/event_converter_evdev.h
@@ -0,0 +1,46 @@
+// 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/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
+
+namespace ui {
+
+class Event;
+class EventModifiersEvdev;
+
+typedef base::Callback<void(Event*)> EventDispatchCallback;
+
+// Base class for device-specific evdev event conversion.
+class EVENTS_OZONE_EVDEV_EXPORT EventConverterEvdev {
+ public:
+ EventConverterEvdev();
+ explicit EventConverterEvdev(const EventDispatchCallback& callback);
+ virtual ~EventConverterEvdev();
+
+ // Start converting events.
+ virtual void Start() = 0;
+
+ // Stop converting events.
+ virtual void Stop() = 0;
+
+ protected:
+ // Dispatches an event using the dispatch-callback set using
+ // |SetDispatchCalback()|.
+ virtual void DispatchEventToCallback(ui::Event* event);
+
+ private:
+ EventDispatchCallback dispatch_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventConverterEvdev);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_EVENT_CONVERTER_EVDEV_H_
diff --git a/chromium/ui/events/ozone/evdev/event_device_info.cc b/chromium/ui/events/ozone/evdev/event_device_info.cc
index 9621646d26b..2734b9d2f49 100644
--- a/chromium/ui/events/ozone/evdev/event_device_info.cc
+++ b/chromium/ui/events/ozone/evdev/event_device_info.cc
@@ -14,8 +14,6 @@ namespace ui {
namespace {
bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) {
- base::ThreadRestrictions::AssertIOAllowed();
-
if (ioctl(fd, EVIOCGBIT(type, size), buf) < 0) {
DLOG(ERROR) << "failed EVIOCGBIT(" << type << ", " << size << ") on fd "
<< fd;
@@ -26,8 +24,6 @@ bool GetEventBits(int fd, unsigned int type, void* buf, unsigned int size) {
}
bool GetPropBits(int fd, void* buf, unsigned int size) {
- base::ThreadRestrictions::AssertIOAllowed();
-
if (ioctl(fd, EVIOCGPROP(size), buf) < 0) {
DLOG(ERROR) << "failed EVIOCGPROP(" << size << ") on fd " << fd;
return false;
@@ -40,6 +36,14 @@ 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() {
@@ -51,6 +55,7 @@ EventDeviceInfo::EventDeviceInfo() {
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() {}
@@ -80,6 +85,11 @@ bool EventDeviceInfo::Initialize(int fd) {
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;
}
@@ -131,4 +141,49 @@ bool EventDeviceInfo::HasProp(unsigned int code) const {
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/chromium/ui/events/ozone/evdev/event_device_info.h b/chromium/ui/events/ozone/evdev/event_device_info.h
index b4b1c05c233..492539bd83a 100644
--- a/chromium/ui/events/ozone/evdev/event_device_info.h
+++ b/chromium/ui/events/ozone/evdev/event_device_info.h
@@ -9,6 +9,7 @@
#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)
@@ -19,7 +20,7 @@ namespace ui {
//
// This stores and queries information about input devices; in
// particular it knows which events the device can generate.
-class EventDeviceInfo {
+class EVENTS_OZONE_EVDEV_EXPORT EventDeviceInfo {
public:
EventDeviceInfo();
~EventDeviceInfo();
@@ -36,9 +37,23 @@ class EventDeviceInfo {
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)];
@@ -49,6 +64,8 @@ class EventDeviceInfo {
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);
};
diff --git a/chromium/ui/events/ozone/evdev/event_factory.cc b/chromium/ui/events/ozone/evdev/event_factory.cc
deleted file mode 100644
index 9c3f9713730..00000000000
--- a/chromium/ui/events/ozone/evdev/event_factory.cc
+++ /dev/null
@@ -1,83 +0,0 @@
-// 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_factory.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/input.h>
-#include <poll.h>
-#include <unistd.h>
-
-#include "base/strings/stringprintf.h"
-#include "ui/events/ozone/evdev/event_device_info.h"
-#include "ui/events/ozone/evdev/key_event_converter.h"
-#include "ui/events/ozone/evdev/touch_event_converter.h"
-#include "ui/events/ozone/event_factory_ozone.h"
-
-namespace ui {
-
-namespace {
-
-bool IsTouchPad(const EventDeviceInfo& devinfo) {
- if (!devinfo.HasEventType(EV_ABS))
- return false;
-
- return devinfo.HasKeyEvent(BTN_LEFT) || devinfo.HasKeyEvent(BTN_MIDDLE) ||
- devinfo.HasKeyEvent(BTN_RIGHT) || devinfo.HasKeyEvent(BTN_TOOL_FINGER);
-}
-
-bool IsTouchScreen(const EventDeviceInfo& devinfo) {
- return devinfo.HasEventType(EV_ABS) && !IsTouchPad(devinfo);
-}
-
-} // namespace
-
-EventFactoryEvdev::EventFactoryEvdev() {}
-
-EventFactoryEvdev::~EventFactoryEvdev() {}
-
-void EventFactoryEvdev::StartProcessingEvents() {
- // The number of devices in the directory is unknown without reading
- // the contents of the directory. Further, with hot-plugging, the entries
- // might decrease during the execution of this loop. So exciting from the
- // loop on the first failure of open below is both cheaper and more
- // reliable.
- for (int id = 0; true; id++) {
- std::string path = base::StringPrintf("/dev/input/event%d", id);
- int fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);
- if (fd < 0) {
- DLOG(ERROR) << "Cannot open '" << path << "': " << strerror(errno);
- break;
- }
-
- EventDeviceInfo devinfo;
- if (!devinfo.Initialize(fd)) {
- DLOG(ERROR) << "failed to get device information for " << path;
- close(fd);
- continue;
- }
-
- if (IsTouchPad(devinfo)) {
- LOG(WARNING) << "touchpad device not supported: " << path;
- close(fd);
- continue;
- }
-
- scoped_ptr<EventConverterOzone> converter;
- // TODO(rjkroege) Add more device types. Support hot-plugging.
- if (IsTouchScreen(devinfo))
- converter.reset(new TouchEventConverterEvdev(fd, id));
- else if (devinfo.HasEventType(EV_KEY))
- converter.reset(new KeyEventConverterEvdev(fd, id, &modifiers_));
-
- if (converter) {
- AddEventConverter(fd, converter.Pass());
- } else {
- close(fd);
- }
- }
-}
-
-} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/event_factory.h b/chromium/ui/events/ozone/evdev/event_factory.h
deleted file mode 100644
index b0e7a0a633f..00000000000
--- a/chromium/ui/events/ozone/evdev/event_factory.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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_EVENT_FACTORY_DELEGATE_EVDEV_H_
-#define UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_
-
-#include "base/compiler_specific.h"
-#include "ui/events/events_export.h"
-#include "ui/events/ozone/evdev/event_modifiers.h"
-#include "ui/events/ozone/event_factory_ozone.h"
-
-namespace ui {
-
-// Ozone events implementation for the Linux input subsystem ("evdev").
-class EVENTS_EXPORT EventFactoryEvdev : public EventFactoryOzone {
- public:
- EventFactoryEvdev();
- virtual ~EventFactoryEvdev();
-
- virtual void StartProcessingEvents() OVERRIDE;
-
- private:
- EventModifiersEvdev modifiers_;
-
- DISALLOW_COPY_AND_ASSIGN(EventFactoryEvdev);
-};
-
-} // namespace ui
-
-#endif // UI_EVENTS_OZONE_EVENT_FACTORY_DELEGATE_EVDEV_H_
diff --git a/chromium/ui/events/ozone/evdev/event_factory_evdev.cc b/chromium/ui/events/ozone/evdev/event_factory_evdev.cc
new file mode 100644
index 00000000000..216488ee5b0
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/event_factory_evdev.cc
@@ -0,0 +1,242 @@
+// 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/threading/worker_pool.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,
+ 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,
+ 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, devinfo, dispatch));
+
+ // Everything else: use KeyEventConverterEvdev.
+ return make_scoped_ptr<EventConverterEvdev>(
+ new KeyEventConverterEvdev(fd, path, 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,
+ 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, 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)
+ : device_manager_(device_manager),
+ cursor_(cursor),
+ dispatch_callback_(
+ base::Bind(base::IgnoreResult(&EventFactoryEvdev::DispatchUiEvent),
+ base::Unretained(this))),
+ weak_ptr_factory_(this) {
+ CHECK(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());
+ CHECK(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();
+}
+
+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_,
+ 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_) {
+ ui_task_runner_ = base::MessageLoopProxy::current();
+ // 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());
+ CHECK(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();
+
+ // 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);
+ }
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/event_factory_evdev.h b/chromium/ui/events/ozone/evdev/event_factory_evdev.h
new file mode 100644
index 00000000000..3eb11d23822
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/event_factory_evdev.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_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/ozone/public/event_factory_ozone.h"
+
+namespace ui {
+
+class CursorDelegateEvdev;
+class DeviceManager;
+
+// Ozone events implementation for the Linux input subsystem ("evdev").
+class EVENTS_OZONE_EVDEV_EXPORT EventFactoryEvdev : public EventFactoryOzone,
+ public DeviceEventObserver,
+ public PlatformEventSource {
+ public:
+ EventFactoryEvdev(CursorDelegateEvdev* cursor,
+ DeviceManager* device_manager);
+ virtual ~EventFactoryEvdev();
+
+ void DispatchUiEvent(Event* event);
+
+ // EventFactoryOzone:
+ virtual void WarpCursorTo(gfx::AcceleratedWidget widget,
+ const gfx::PointF& location) OVERRIDE;
+
+ 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);
+
+ // 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_;
+
+ // 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/chromium/ui/events/ozone/evdev/event_modifiers.cc b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.cc
index 220ec083c3b..d867b5eef7d 100644
--- a/chromium/ui/events/ozone/evdev/event_modifiers.cc
+++ b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.cc
@@ -1,8 +1,8 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
#include <linux/input.h>
@@ -71,4 +71,9 @@ void EventModifiersEvdev::UpdateFlags(unsigned int modifier) {
int EventModifiersEvdev::GetModifierFlags() { return modifier_flags_; }
+// static
+int EventModifiersEvdev::GetEventFlagFromModifier(unsigned int modifier) {
+ return kEventFlagFromModifiers[modifier];
+}
+
} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/event_modifiers.h b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.h
index 225af026fbc..d10bb8cc4db 100644
--- a/chromium/ui/events/ozone/evdev/event_modifiers.h
+++ b/chromium/ui/events/ozone/evdev/event_modifiers_evdev.h
@@ -1,12 +1,12 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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_H_
-#define UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_H_
+#ifndef UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
+#define UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
-#include "ui/events/events_export.h"
-#include "ui/events/ozone/event_converter_ozone.h"
+#include "base/basictypes.h"
+#include "ui/events/ozone/evdev/events_ozone_evdev_export.h"
namespace ui {
@@ -39,7 +39,7 @@ enum {
// 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_EXPORT EventModifiersEvdev {
+class EVENTS_OZONE_EVDEV_EXPORT EventModifiersEvdev {
public:
EventModifiersEvdev();
~EventModifiersEvdev();
@@ -53,6 +53,9 @@ class EVENTS_EXPORT EventModifiersEvdev {
// 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];
@@ -71,4 +74,4 @@ class EVENTS_EXPORT EventModifiersEvdev {
} // namspace ui
-#endif // UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_H_
+#endif // UI_EVENTS_OZONE_EVDEV_EVENT_MODIFIERS_EVDEV_H_
diff --git a/chromium/ui/events/ozone/evdev/events_ozone_evdev_export.h b/chromium/ui/events/ozone/evdev/events_ozone_evdev_export.h
new file mode 100644
index 00000000000..4a4cbacee64
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/evdev/key_event_converter.h b/chromium/ui/events/ozone/evdev/key_event_converter.h
deleted file mode 100644
index a544b02104c..00000000000
--- a/chromium/ui/events/ozone/evdev/key_event_converter.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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_KEY_EVENT_CONVERTER_EVDEV_H_
-#define UI_EVENTS_OZONE_EVDEV_KEY_EVENT_CONVERTER_EVDEV_H_
-
-#include "ui/events/event.h"
-#include "ui/events/events_export.h"
-#include "ui/events/ozone/evdev/event_modifiers.h"
-#include "ui/events/ozone/event_converter_ozone.h"
-
-struct input_event;
-
-namespace ui {
-
-class EVENTS_EXPORT KeyEventConverterEvdev : public EventConverterOzone {
- public:
- KeyEventConverterEvdev(int fd, int id, EventModifiersEvdev* modifiers);
- virtual ~KeyEventConverterEvdev();
-
- // Overidden from base::MessagePumpLibevent::Watcher.
- virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
- virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
-
- void ProcessEvents(const struct input_event* inputs, int count);
-
- private:
- // File descriptor for the /dev/input/event* instance.
- int fd_;
-
- // Number corresponding to * in the source evdev device: /dev/input/event*
- int id_;
-
- // Shared modifier state.
- EventModifiersEvdev* modifiers_;
-
- 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/chromium/ui/events/ozone/evdev/key_event_converter.cc b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.cc
index b739b34a53c..2e6f31e052a 100644
--- a/chromium/ui/events/ozone/evdev/key_event_converter.cc
+++ b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.cc
@@ -1,14 +1,17 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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.h"
+#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/keyboard_codes.h"
-#include "ui/events/ozone/evdev/event_modifiers.h"
+#include "ui/events/ozone/evdev/event_modifiers_evdev.h"
+#include "ui/ozone/public/event_factory_ozone.h"
namespace ui {
@@ -184,23 +187,43 @@ bool IsLockButton(unsigned int code) { return code == KEY_CAPSLOCK; }
} // namespace
-KeyEventConverterEvdev::KeyEventConverterEvdev(int fd,
- int id,
- EventModifiersEvdev* modifiers)
- : fd_(fd), id_(id), modifiers_(modifiers) {
+KeyEventConverterEvdev::KeyEventConverterEvdev(
+ int fd,
+ base::FilePath path,
+ EventModifiersEvdev* modifiers,
+ const EventDispatchCallback& callback)
+ : EventConverterEvdev(callback),
+ fd_(fd),
+ path_(path),
+ modifiers_(modifiers) {
// TODO(spang): Initialize modifiers using EVIOCGKEY.
}
KeyEventConverterEvdev::~KeyEventConverterEvdev() {
- if (fd_ >= 0 && close(fd_) < 0)
- DLOG(WARNING) << "failed close on /dev/input/event" << id_;
+ Stop();
+ close(fd_);
+}
+
+void KeyEventConverterEvdev::Start() {
+ base::MessageLoopForUI::current()->WatchFileDescriptor(
+ fd_, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this);
+}
+
+void KeyEventConverterEvdev::Stop() {
+ controller_.StopWatchingFileDescriptor();
}
void KeyEventConverterEvdev::OnFileCanReadWithoutBlocking(int fd) {
input_event inputs[4];
ssize_t read_size = read(fd, inputs, sizeof(inputs));
- if (read_size <= 0)
+ if (read_size < 0) {
+ if (errno == EINTR || errno == EAGAIN)
+ return;
+ if (errno != ENODEV)
+ PLOG(ERROR) << "error reading device " << path_.value();
+ Stop();
return;
+ }
CHECK_EQ(read_size % sizeof(*inputs), 0u);
ProcessEvents(inputs, read_size / sizeof(*inputs));
@@ -240,9 +263,9 @@ void KeyEventConverterEvdev::ConvertKeyEvent(int key, int value) {
int flags = modifiers_->GetModifierFlags();
- scoped_ptr<KeyEvent> key_event(
- new KeyEvent(down ? ET_KEY_PRESSED : ET_KEY_RELEASED, code, flags, true));
- DispatchEvent(key_event.PassAs<ui::Event>());
+ KeyEvent key_event(
+ down ? ET_KEY_PRESSED : ET_KEY_RELEASED, code, flags, false);
+ DispatchEventToCallback(&key_event);
}
} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/key_event_converter_evdev.h b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.h
new file mode 100644
index 00000000000..26615d94942
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/key_event_converter_evdev.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_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 base::MessagePumpLibevent::Watcher {
+ public:
+ KeyEventConverterEvdev(int fd,
+ base::FilePath path,
+ EventModifiersEvdev* modifiers,
+ const EventDispatchCallback& dispatch);
+ virtual ~KeyEventConverterEvdev();
+
+ // Start & stop watching for events.
+ virtual void Start() OVERRIDE;
+ virtual void Stop() OVERRIDE;
+
+ // Overidden from base::MessagePumpLibevent::Watcher.
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
+
+ void ProcessEvents(const struct input_event* inputs, int count);
+
+ private:
+ // File descriptor for the /dev/input/event* instance.
+ int fd_;
+
+ // Path to input device.
+ base::FilePath path_;
+
+ // 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/chromium/ui/events/ozone/evdev/key_event_converter_unittest.cc b/chromium/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc
index 472139078bd..88ee64609ed 100644
--- a/chromium/ui/events/ozone/evdev/key_event_converter_unittest.cc
+++ b/chromium/ui/events/ozone/evdev/key_event_converter_evdev_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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.
@@ -6,28 +6,34 @@
#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.h"
+#include "ui/events/ozone/evdev/key_event_converter_evdev.h"
namespace ui {
-const int kInvalidFileDescriptor = -1;
-const int kTestDeviceId = 0;
+const char kTestDevicePath[] = "/dev/input/test-device";
class MockKeyEventConverterEvdev : public KeyEventConverterEvdev {
public:
- MockKeyEventConverterEvdev(EventModifiersEvdev* modifiers)
- : KeyEventConverterEvdev(kInvalidFileDescriptor,
- kTestDeviceId,
- modifiers) {}
+ MockKeyEventConverterEvdev(int fd, EventModifiersEvdev* modifiers)
+ : KeyEventConverterEvdev(fd,
+ base::FilePath(kTestDevicePath),
+ modifiers,
+ EventDispatchCallback()) {
+ Start();
+ }
virtual ~MockKeyEventConverterEvdev() {};
unsigned size() { return dispatched_events_.size(); }
- KeyEvent* event(unsigned index) { return dispatched_events_[index]; }
+ KeyEvent* event(unsigned index) {
+ CHECK_GT(dispatched_events_.size(), index);
+ return dispatched_events_[index];
+ }
- virtual void DispatchEvent(scoped_ptr<Event> event) OVERRIDE;
+ virtual void DispatchEventToCallback(Event* event) OVERRIDE;
private:
ScopedVector<KeyEvent> dispatched_events_;
@@ -35,8 +41,8 @@ class MockKeyEventConverterEvdev : public KeyEventConverterEvdev {
DISALLOW_COPY_AND_ASSIGN(MockKeyEventConverterEvdev);
};
-void MockKeyEventConverterEvdev::DispatchEvent(scoped_ptr<Event> event) {
- dispatched_events_.push_back(static_cast<KeyEvent*>(event.release()));
+void MockKeyEventConverterEvdev::DispatchEventToCallback(Event* event) {
+ dispatched_events_.push_back(new KeyEvent(*static_cast<KeyEvent*>(event)));
}
} // namespace ui
@@ -48,20 +54,36 @@ class KeyEventConverterEvdevTest : public testing::Test {
// 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(modifiers_);
+ 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);
};
diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc
new file mode 100644
index 00000000000..86f8834d389
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.cc
@@ -0,0 +1,107 @@
+// 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,
+ scoped_ptr<Delegate> delegate)
+ : path_(path), 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::Start() {
+ base::MessageLoopForUI::current()->WatchFileDescriptor(
+ evdev_.fd,
+ true,
+ base::MessagePumpLibevent::WATCH_READ,
+ &controller_,
+ this);
+}
+
+void EventReaderLibevdevCros::Stop() {
+ controller_.StopWatchingFileDescriptor();
+}
+
+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;
+ }
+}
+
+void EventReaderLibevdevCros::OnFileCanWriteWithoutBlocking(int fd) {
+ NOTREACHED();
+}
+
+// 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/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h
new file mode 100644
index 00000000000..1631c5b685b
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/libgestures_glue/event_reader_libevdev_cros.h
@@ -0,0 +1,78 @@
+// 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 base::MessagePumpLibevent::Watcher,
+ 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,
+ scoped_ptr<Delegate> delegate);
+ ~EventReaderLibevdevCros();
+
+ // Overridden from ui::EventDeviceEvdev.
+ void Start() OVERRIDE;
+ void Stop() OVERRIDE;
+
+ // Overidden from MessagePumpLibevent::Watcher.
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
+ virtual void OnFileCanWriteWithoutBlocking(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_;
+
+ // Controller for watching the input fd.
+ base::MessagePumpLibevent::FileDescriptorWatcher controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(EventReaderLibevdevCros);
+};
+
+} // namspace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_LIBGESTURES_GLUE_EVENT_READER_LIBEVDEV_CROS_H_
diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
new file mode 100644
index 00000000000..c41a04d2f08
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.cc
@@ -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/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;
+
+} // 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) {
+ CHECK(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.
+ CHECK(!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:
+ case kGestureTypeFling:
+ case kGestureTypeSwipe:
+ case kGestureTypeSwipeLift:
+ case kGestureTypePinch:
+ case kGestureTypeMetrics:
+ // TODO(spang): Support remaining gestures.
+ NOTIMPLEMENTED();
+ 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) {
+ if (!cursor_)
+ return; // No cursor!
+ DVLOG(3) << base::StringPrintf("Gesture Scroll: (%f, %f) [%f, %f]",
+ scroll->dx,
+ scroll->dy,
+ scroll->ordinal_dx,
+ scroll->ordinal_dy);
+ // 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);
+ // 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::Dispatch(Event* event) {
+ dispatch_callback_.Run(event);
+}
+
+void GestureInterpreterLibevdevCros::DispatchMouseButton(unsigned int modifier,
+ bool down) {
+ if (!cursor_)
+ return; // No cursor!
+ 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);
+
+ // This hack is necessary to trigger setting the repeat count.
+ // TODO(spang): Fix it.
+ MouseEvent event2(&event);
+ Dispatch(&event2);
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.h
new file mode 100644
index 00000000000..30d8f62ab90
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_interpreter_libevdev_cros.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_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 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/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.cc
new file mode 100644
index 00000000000..009fc939020
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_logging.h
new file mode 100644
index 00000000000..a5f543556ab
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.cc
new file mode 100644
index 00000000000..92f15b77825
--- /dev/null
+++ b/chromium/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;
+ CHECK(!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/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h b/chromium/ui/events/ozone/evdev/libgestures_glue/gesture_timer_provider.h
new file mode 100644
index 00000000000..edc20ba9c07
--- /dev/null
+++ b/chromium/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/chromium/ui/events/ozone/evdev/touch_event_converter.cc b/chromium/ui/events/ozone/evdev/touch_event_converter.cc
deleted file mode 100644
index dc3c30a43f6..00000000000
--- a/chromium/ui/events/ozone/evdev/touch_event_converter.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-// 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/touch_event_converter.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/logging.h"
-#include "base/message_loop/message_loop.h"
-#include "base/message_loop/message_pump_ozone.h"
-#include "ui/events/event.h"
-#include "ui/events/event_constants.h"
-#include "ui/gfx/ozone/surface_factory_ozone.h"
-
-namespace {
-
-// Number is determined empirically.
-// TODO(rjkroege): Configure this per device.
-const float kFingerWidth = 25.f;
-
-} // namespace
-
-namespace ui {
-
-TouchEventConverterEvdev::TouchEventConverterEvdev(int fd, int id)
- : pressure_min_(0),
- pressure_max_(0),
- x_scale_(1.),
- y_scale_(1.),
- x_max_(std::numeric_limits<int>::max()),
- y_max_(std::numeric_limits<int>::max()),
- current_slot_(0),
- fd_(fd),
- id_(id) {
- Init();
-}
-
-TouchEventConverterEvdev::~TouchEventConverterEvdev() {
- if (fd_ >= 0 && close(fd_) < 0)
- DLOG(WARNING) << "failed close on /dev/input/event" << id_;
-}
-
-void TouchEventConverterEvdev::Init() {
- input_absinfo abs = {};
- if (ioctl(fd_, EVIOCGABS(ABS_MT_SLOT), &abs) != -1) {
- CHECK_GE(abs.maximum, abs.minimum);
- CHECK_GE(abs.minimum, 0);
- } else {
- DLOG(WARNING) << "failed ioctl EVIOCGABS ABS_MT_SLOT event" << id_;
- }
- if (ioctl(fd_, EVIOCGABS(ABS_MT_PRESSURE), &abs) != -1) {
- pressure_min_ = abs.minimum;
- pressure_max_ = abs.maximum;
- } else {
- DLOG(WARNING) << "failed ioctl EVIOCGABS ABS_MT_PRESSURE event" << id_;
- }
- int x_min = 0, x_max = 0;
- if (ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_X), &abs) != -1) {
- x_min = abs.minimum;
- x_max = abs.maximum;
- } else {
- LOG(WARNING) << "failed ioctl EVIOCGABS ABS_X event" << id_;
- }
- int y_min = 0, y_max = 0;
- if (ioctl(fd_, EVIOCGABS(ABS_MT_POSITION_Y), &abs) != -1) {
- y_min = abs.minimum;
- y_max = abs.maximum;
- } else {
- LOG(WARNING) << "failed ioctl EVIOCGABS ABS_Y event" << id_;
- }
- if (x_max && y_max && gfx::SurfaceFactoryOzone::GetInstance()) {
- const char* display =
- gfx::SurfaceFactoryOzone::GetInstance()->DefaultDisplaySpec();
- int screen_width, screen_height;
- int sc = sscanf(display, "%dx%d", &screen_width, &screen_height);
- if (sc == 2) {
- x_scale_ = (double)screen_width / (x_max - x_min);
- y_scale_ = (double)screen_height / (y_max - y_min);
- x_max_ = screen_width - 1;
- y_max_ = screen_height - 1;
- LOG(INFO) << "touch input x_scale=" << x_scale_
- << " y_scale=" << y_scale_;
- } else {
- LOG(WARNING) << "malformed display spec from "
- << "SurfaceFactoryOzone::DefaultDisplaySpec";
- }
- }
-}
-
-void TouchEventConverterEvdev::OnFileCanWriteWithoutBlocking(int /* fd */) {
- // Read-only file-descriptors.
- NOTREACHED();
-}
-
-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)
- return;
-
- for (unsigned i = 0; i < read_size / sizeof(*inputs); i++) {
- const input_event& input = inputs[i];
- if (input.type == EV_ABS) {
- switch (input.code) {
- case ABS_MT_TOUCH_MAJOR:
- altered_slots_.set(current_slot_);
- events_[current_slot_].major_ = input.value;
- break;
- case ABS_X:
- case ABS_MT_POSITION_X:
- altered_slots_.set(current_slot_);
- events_[current_slot_].x_ = roundf(input.value * x_scale_);
- break;
- case ABS_Y:
- case ABS_MT_POSITION_Y:
- altered_slots_.set(current_slot_);
- events_[current_slot_].y_ = roundf(input.value * y_scale_);
- 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:
- case ABS_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:
- NOTREACHED() << "invalid code for EV_ABS: " << input.code;
- }
- } else if (input.type == EV_SYN) {
- switch (input.code) {
- case SYN_REPORT:
- for (int j = 0; j < MAX_FINGERS; j++) {
- if (altered_slots_[j]) {
- // TODO(rjkroege): Support elliptical finger regions.
- scoped_ptr<TouchEvent> tev(new TouchEvent(
- events_[j].type_,
- gfx::Point(std::min(x_max_, events_[j].x_),
- std::min(y_max_, events_[j].y_)),
- /* flags */ 0,
- /* touch_id */ j,
- base::TimeDelta::FromMicroseconds(
- input.time.tv_sec * 1000000 + input.time.tv_usec),
- events_[j].pressure_ * kFingerWidth,
- events_[j].pressure_ * kFingerWidth,
- /* angle */ 0.,
- events_[j].pressure_));
- DispatchEvent(tev.PassAs<ui::Event>());
-
- // Subsequent events for this finger will be touch-move until it
- // is released.
- events_[j].type_ = ET_TOUCH_MOVED;
- }
- }
- altered_slots_.reset();
- break;
- case SYN_MT_REPORT:
- case SYN_CONFIG:
- case SYN_DROPPED:
- NOTREACHED() << "invalid code for EV_SYN: " << input.code;
- break;
- }
- } else if (input.type == EV_KEY) {
- switch (input.code) {
- case BTN_TOUCH:
- break;
- default:
- NOTREACHED() << "invalid code for EV_KEY: " << input.code;
- }
- } else {
- NOTREACHED() << "invalid type: " << input.type;
- }
- }
-}
-
-} // namespace ui
diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter.h b/chromium/ui/events/ozone/evdev/touch_event_converter.h
deleted file mode 100644
index 7ebac408d8f..00000000000
--- a/chromium/ui/events/ozone/evdev/touch_event_converter.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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_TOUCH_EVENT_CONVERTER_EVDEV_H_
-#define UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_
-
-#include <bitset>
-
-#include "base/compiler_specific.h"
-#include "ui/events/event_constants.h"
-#include "ui/events/events_export.h"
-#include "ui/events/ozone/event_converter_ozone.h"
-
-namespace ui {
-
-class TouchEvent;
-
-class EVENTS_EXPORT TouchEventConverterEvdev : public EventConverterOzone {
- public:
- enum {
- MAX_FINGERS = 11
- };
- TouchEventConverterEvdev(int fd, int id);
- virtual ~TouchEventConverterEvdev();
-
- private:
- friend class MockTouchEventConverterEvdev;
-
- // Unsafe part of initialization.
- void Init();
-
- // Overidden from base::MessagePumpLibevent::Watcher.
- virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
- virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
-
- // Pressure values.
- int pressure_min_;
- int pressure_max_; // Used to normalize pressure values.
-
- // Touch scaling.
- float x_scale_;
- float y_scale_;
-
- // Maximum coordinate-values allowed for the events.
- int x_max_;
- int y_max_;
-
- // Touch point currently being updated from the /dev/input/event* stream.
- int current_slot_;
-
- // File descriptor for the /dev/input/event* instance.
- int fd_;
-
- // Number corresponding to * in the source evdev device: /dev/input/event*
- int id_;
-
- // Bit field tracking which in-progress touch points have been modified
- // without a syn event.
- std::bitset<MAX_FINGERS> altered_slots_;
-
- struct InProgressEvents {
- int x_;
- int 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_;
- int major_;
- 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/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc
new file mode 100644
index 00000000000..9cfab5bf11c
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.cc
@@ -0,0 +1,307 @@
+// 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"
+#include "ui/ozone/public/event_factory_ozone.h"
+
+namespace {
+
+// Number is determined empirically.
+// TODO(rjkroege): Configure this per device.
+const float kFingerWidth = 25.f;
+
+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;
+}
+
+} // namespace
+
+namespace ui {
+
+TouchEventConverterEvdev::TouchEventConverterEvdev(
+ int fd,
+ base::FilePath path,
+ const EventDeviceInfo& info,
+ const EventDispatchCallback& callback)
+ : EventConverterEvdev(callback),
+ syn_dropped_(false),
+ is_type_a_(false),
+ current_slot_(0),
+ fd_(fd),
+ path_(path) {
+ 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,
+ x_min_pixels_ = x_min_tuxels_,
+ x_num_pixels_ = x_num_tuxels_,
+ y_min_pixels_ = y_min_tuxels_,
+ y_num_pixels_ = 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);
+}
+
+void TouchEventConverterEvdev::Start() {
+ base::MessageLoopForUI::current()->WatchFileDescriptor(
+ fd_, true, base::MessagePumpLibevent::WATCH_READ, &controller_, this);
+}
+
+void TouchEventConverterEvdev::Stop() {
+ controller_.StopWatchingFileDescriptor();
+}
+
+bool TouchEventConverterEvdev::Reinitialize() {
+ EventDeviceInfo info;
+ if (info.Initialize(fd_)) {
+ Init(info);
+ return true;
+ }
+ return false;
+}
+
+void TouchEventConverterEvdev::OnFileCanWriteWithoutBlocking(int /* fd */) {
+ // Read-only file-descriptors.
+ NOTREACHED();
+}
+
+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_);
+ events_[current_slot_].major_ = input.value;
+ break;
+ case ABS_X:
+ 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_Y:
+ 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:
+ case ABS_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:
+ NOTIMPLEMENTED() << "invalid 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,
+ events_[i].pressure_ * kFingerWidth,
+ events_[i].pressure_ * kFingerWidth,
+ /* angle */ 0.,
+ events_[i].pressure_);
+ DispatchEventToCallback(&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/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h
new file mode 100644
index 00000000000..668901d5f9d
--- /dev/null
+++ b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev.h
@@ -0,0 +1,118 @@
+// 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 base::MessagePumpLibevent::Watcher {
+ public:
+ enum {
+ MAX_FINGERS = 11
+ };
+ TouchEventConverterEvdev(int fd,
+ base::FilePath path,
+ const EventDeviceInfo& info,
+ const EventDispatchCallback& dispatch);
+ virtual ~TouchEventConverterEvdev();
+
+ // Start & stop watching for events.
+ virtual void Start() OVERRIDE;
+ virtual void Stop() 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 void OnFileCanWriteWithoutBlocking(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);
+
+ // 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_;
+
+ // Touch point currently being updated from the /dev/input/event* stream.
+ int current_slot_;
+
+ // File descriptor for the /dev/input/event* instance.
+ int fd_;
+
+ // Path to input device.
+ base::FilePath path_;
+
+ // 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_;
+ int major_;
+ float pressure_;
+ };
+
+ // In-progress touch points.
+ InProgressEvents events_[MAX_FINGERS];
+
+ // Controller for watching the input fd.
+ base::MessagePumpLibevent::FileDescriptorWatcher controller_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdev);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_EVENT_CONVERTER_EVDEV_H_
diff --git a/chromium/ui/events/ozone/evdev/touch_event_converter_unittest.cc b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
index d99ab720115..ce2957a1ece 100644
--- a/chromium/ui/events/ozone/evdev/touch_event_converter_unittest.cc
+++ b/chromium/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
+// 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.
@@ -11,13 +11,14 @@
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
-#include "base/message_loop/message_loop.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.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 {
@@ -28,14 +29,15 @@ static int SetNonBlocking(int fd) {
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
+const char kTestDevicePath[] = "/dev/input/test-device";
+
} // namespace
namespace ui {
-class MockTouchEventConverterEvdev : public TouchEventConverterEvdev,
- public base::MessageLoop::Dispatcher {
+class MockTouchEventConverterEvdev : public TouchEventConverterEvdev {
public:
- MockTouchEventConverterEvdev(int a, int b);
+ MockTouchEventConverterEvdev(int fd, base::FilePath path);
virtual ~MockTouchEventConverterEvdev() {};
void ConfigureReadMock(struct input_event* queue,
@@ -51,7 +53,12 @@ class MockTouchEventConverterEvdev : public TouchEventConverterEvdev,
base::RunLoop().RunUntilIdle();
}
- virtual bool Dispatch(const base::NativeEvent& event) OVERRIDE;
+ void DispatchCallback(Event* event) {
+ dispatched_events_.push_back(
+ new TouchEvent(*static_cast<TouchEvent*>(event)));
+ }
+
+ virtual bool Reinitialize() OVERRIDE { return true; }
private:
int read_pipe_;
@@ -62,15 +69,27 @@ class MockTouchEventConverterEvdev : public TouchEventConverterEvdev,
DISALLOW_COPY_AND_ASSIGN(MockTouchEventConverterEvdev);
};
-MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int a, int b)
- : TouchEventConverterEvdev(a, b) {
+MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int fd,
+ base::FilePath path)
+ : TouchEventConverterEvdev(
+ fd,
+ path,
+ 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))
- NOTREACHED() << "failed pipe(): " << strerror(errno);
+ PLOG(FATAL) << "failed pipe";
DCHECK(SetNonBlocking(fds[0]) == 0)
<< "SetNonBlocking for pipe fd[0] failed, errno: " << errno;
@@ -80,12 +99,6 @@ MockTouchEventConverterEvdev::MockTouchEventConverterEvdev(int a, int b)
write_pipe_ = fds[1];
}
-bool MockTouchEventConverterEvdev::Dispatch(const base::NativeEvent& event) {
- ui::TouchEvent* ev = new ui::TouchEvent(event);
- dispatched_events_.push_back(ev);
- return true;
-}
-
void MockTouchEventConverterEvdev::ConfigureReadMock(struct input_event* queue,
long read_this_many,
long queue_index) {
@@ -106,10 +119,18 @@ class TouchEventConverterEvdevTest : public testing::Test {
// Overridden from testing::Test:
virtual void SetUp() OVERRIDE {
- loop_ = new base::MessageLoop(base::MessageLoop::TYPE_UI);
- device_ = new ui::MockTouchEventConverterEvdev(-1, 2);
- base::MessagePumpOzone::Current()->AddDispatcherForRootWindow(device_);
+ // 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_;
@@ -120,6 +141,10 @@ class TouchEventConverterEvdevTest : public testing::Test {
private:
base::MessageLoop* loop_;
ui::MockTouchEventConverterEvdev* device_;
+
+ int events_out_;
+ int events_in_;
+
DISALLOW_COPY_AND_ASSIGN(TouchEventConverterEvdevTest);
};
@@ -396,3 +421,61 @@ TEST_F(TouchEventConverterEvdevTest, TwoFingerGesture) {
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());
+}
diff --git a/chromium/ui/events/ozone/event_converter_ozone.cc b/chromium/ui/events/ozone/event_converter_ozone.cc
deleted file mode 100644
index 2bb6164e80b..00000000000
--- a/chromium/ui/events/ozone/event_converter_ozone.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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/event_converter_ozone.h"
-
-#include "base/bind.h"
-#include "base/message_loop/message_loop.h"
-#include "base/message_loop/message_pump_ozone.h"
-#include "ui/events/event.h"
-
-namespace {
-
-void DispatchEventHelper(scoped_ptr<ui::Event> key) {
- base::MessagePumpOzone::Current()->Dispatch(key.get());
-}
-
-} // namespace
-
-namespace ui {
-
-EventConverterOzone::EventConverterOzone() {
-}
-
-EventConverterOzone::~EventConverterOzone() {
-}
-
-void EventConverterOzone::DispatchEvent(scoped_ptr<ui::Event> event) {
- base::MessageLoop::current()->PostTask(
- FROM_HERE, base::Bind(&DispatchEventHelper, base::Passed(&event)));
-}
-
-} // namespace ui
diff --git a/chromium/ui/events/ozone/event_converter_ozone.h b/chromium/ui/events/ozone/event_converter_ozone.h
deleted file mode 100644
index 90826c26580..00000000000
--- a/chromium/ui/events/ozone/event_converter_ozone.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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_OZONE_EVENT_CONVERTER_OZONE_H_
-#define UI_EVENTS_OZONE_EVENT_CONVERTER_OZONE_H_
-
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_pump_libevent.h"
-#include "ui/events/events_export.h"
-
-namespace ui {
-class Event;
-
-// In ozone, Chrome reads events from file descriptors created from Linux device
-// drivers. The |MessagePumpLibevent::Watcher| parent class provides the
-// functionality to watch a file descriptor for the arrival of new data and
-// notify its subclasses. Device-specific event converters turn bytes read from
-// the file descriptor into |ui::Event| instances. This class provides the
-// functionality needed in common across all converters: dispatching the
-// |ui::Event| to aura.
-class EVENTS_EXPORT EventConverterOzone
- : public base::MessagePumpLibevent::Watcher {
- public:
- EventConverterOzone();
- virtual ~EventConverterOzone();
-
- protected:
- // Subclasses should use this method to post a task that will dispatch
- // |event| from the UI message loop. This method takes ownership of
- // |event|. |event| will be deleted at the end of the posted task.
- virtual void DispatchEvent(scoped_ptr<ui::Event> event);
-
- private:
- DISALLOW_COPY_AND_ASSIGN(EventConverterOzone);
-};
-
-} // namespace ui
-
-#endif // UI_EVENTS_OZONE_EVENT_CONVERTER_OZONE_H_
diff --git a/chromium/ui/events/ozone/event_factory_ozone.cc b/chromium/ui/events/ozone/event_factory_ozone.cc
deleted file mode 100644
index b051e21daee..00000000000
--- a/chromium/ui/events/ozone/event_factory_ozone.cc
+++ /dev/null
@@ -1,61 +0,0 @@
-// 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/event_factory_ozone.h"
-
-#include "base/command_line.h"
-#include "base/message_loop/message_pump_ozone.h"
-#include "base/stl_util.h"
-#include "base/strings/stringprintf.h"
-#include "ui/events/event_switches.h"
-
-namespace ui {
-
-// static
-EventFactoryOzone* EventFactoryOzone::impl_ = NULL;
-
-EventFactoryOzone::EventFactoryOzone() {}
-
-EventFactoryOzone::~EventFactoryOzone() {
- // Always delete watchers before converters to prevent a possible race.
- STLDeleteValues<std::map<int, FDWatcher> >(&watchers_);
- STLDeleteValues<std::map<int, Converter> >(&converters_);
-}
-
-EventFactoryOzone* EventFactoryOzone::GetInstance() {
- CHECK(impl_) << "No EventFactoryOzone implementation set.";
- return impl_;
-}
-
-void EventFactoryOzone::SetInstance(EventFactoryOzone* impl) { impl_ = impl; }
-
-void EventFactoryOzone::StartProcessingEvents() {}
-
-void EventFactoryOzone::AddEventConverter(
- int fd,
- scoped_ptr<EventConverterOzone> converter) {
- CHECK(watchers_.count(fd) == 0 && converters_.count(fd) == 0);
-
- FDWatcher watcher = new base::MessagePumpLibevent::FileDescriptorWatcher();
-
- base::MessagePumpOzone::Current()->WatchFileDescriptor(
- fd,
- true,
- base::MessagePumpLibevent::WATCH_READ,
- watcher,
- converter.get());
-
- converters_[fd] = converter.release();
- watchers_[fd] = watcher;
-}
-
-void EventFactoryOzone::RemoveEventConverter(int fd) {
- // Always delete watchers before converters to prevent a possible race.
- delete watchers_[fd];
- delete converters_[fd];
- watchers_.erase(fd);
- converters_.erase(fd);
-}
-
-} // namespace ui
diff --git a/chromium/ui/events/ozone/event_factory_ozone.h b/chromium/ui/events/ozone/event_factory_ozone.h
deleted file mode 100644
index 389946ae339..00000000000
--- a/chromium/ui/events/ozone/event_factory_ozone.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// 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_OZONE_EVENT_FACTORY_OZONE_H_
-#define UI_EVENTS_OZONE_EVENT_FACTORY_OZONE_H_
-
-#include <map>
-
-#include "base/memory/scoped_ptr.h"
-#include "base/message_loop/message_pump_libevent.h"
-#include "ui/events/events_export.h"
-#include "ui/events/ozone/event_converter_ozone.h"
-
-namespace ui {
-
-// Creates and dispatches |ui.Event|'s. Ozone assumes that events arrive on file
-// descriptors with one |EventConverterOzone| instance for each descriptor.
-// Ozone presumes that the set of file desctiprtors can vary at runtime so this
-// class supports dynamically adding and removing |EventConverterOzone|
-// instances as necessary.
-class EVENTS_EXPORT EventFactoryOzone {
- public:
- EventFactoryOzone();
- virtual ~EventFactoryOzone();
-
- // Called from RootWindowHostOzone to initialize and start processing events.
- // This should create the initial set of converters, and potentially arrange
- // for more converters to be created as new event sources become available.
- // No events processing should happen until this is called. All processes have
- // an EventFactoryOzone but not all of them should process events. In chrome,
- // events are dispatched in the browser process on the UI thread.
- virtual void StartProcessingEvents();
-
- // Returns the static instance last set using SetInstance().
- static EventFactoryOzone* GetInstance();
-
- // Sets the implementation delegate. Ownership is retained by the caller.
- static void SetInstance(EventFactoryOzone*);
-
- // Add an |EventConverterOzone| instances for the given file descriptor.
- // Transfers ownership of the |EventConverterOzone| to this class.
- void AddEventConverter(int fd, scoped_ptr<EventConverterOzone> converter);
-
- // Remote the |EventConverterOzone|
- void RemoveEventConverter(int fd);
-
- private:
- // |EventConverterOzone| for each file descriptor.
- typedef EventConverterOzone* Converter;
- typedef base::MessagePumpLibevent::FileDescriptorWatcher* FDWatcher;
- std::map<int, Converter> converters_;
- std::map<int, FDWatcher> watchers_;
-
- static EventFactoryOzone* impl_; // not owned
-
- DISALLOW_COPY_AND_ASSIGN(EventFactoryOzone);
-};
-
-} // namespace ui
-
-#endif // UI_EVENTS_OZONE_EVENT_FACTORY_OZONE_H_
diff --git a/chromium/ui/events/ozone/events_ozone.cc b/chromium/ui/events/ozone/events_ozone.cc
index 4655d411923..7d3f627d078 100644
--- a/chromium/ui/events/ozone/events_ozone.cc
+++ b/chromium/ui/events/ozone/events_ozone.cc
@@ -58,9 +58,10 @@ const char* CodeFromNative(const base::NativeEvent& native_event) {
return event->code().c_str();
}
-bool IsMouseEvent(const base::NativeEvent& native_event) {
- const ui::Event* e = static_cast<const ui::Event*>(native_event);
- return e->IsMouseEvent();
+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();
}
gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
@@ -70,6 +71,13 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
return event->offset();
}
+base::NativeEvent CopyNativeEvent(const base::NativeEvent& event) {
+ return NULL;
+}
+
+void ReleaseCopiedNativeEvent(const base::NativeEvent& event) {
+}
+
void ClearTouchIdIfReleased(const base::NativeEvent& xev) {
}
@@ -145,16 +153,6 @@ bool IsTouchpadEvent(const base::NativeEvent& event) {
return false;
}
-bool IsNoopEvent(const base::NativeEvent& event) {
- NOTIMPLEMENTED();
- return false;
-}
-
-base::NativeEvent CreateNoopEvent() {
- NOTIMPLEMENTED();
- return NULL;
-}
-
int GetModifiersFromKeyState() {
NOTIMPLEMENTED();
return 0;
diff --git a/chromium/ui/events/ozone/events_ozone.gyp b/chromium/ui/events/ozone/events_ozone.gyp
new file mode 100644
index 00000000000..e20e75af203
--- /dev/null
+++ b/chromium/ui/events/ozone/events_ozone.gyp
@@ -0,0 +1,97 @@
+# 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',
+ '../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/chromium/ui/events/ozone/events_ozone_export.h b/chromium/ui/events/ozone/events_ozone_export.h
new file mode 100644
index 00000000000..bda8814eda9
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/BUILD.gn b/chromium/ui/events/platform/BUILD.gn
new file mode 100644
index 00000000000..3a20219a33c
--- /dev/null
+++ b/chromium/ui/events/platform/BUILD.gn
@@ -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.
+
+import("//build/config/ui.gni")
+
+component("platform") {
+ 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",
+ ]
+
+ defines = [
+ "EVENTS_IMPLEMENTATION",
+ ]
+
+ deps = [
+ "//base"
+ ]
+
+ if (use_x11) {
+ sources -= [
+ "platform_event_source_stub.cc",
+ ]
+ }
+}
diff --git a/chromium/ui/events/platform/events_platform.gyp b/chromium/ui/events/platform/events_platform.gyp
new file mode 100644
index 00000000000..d1b3f6bfe3e
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_dispatcher.h b/chromium/ui/events/platform/platform_event_dispatcher.h
new file mode 100644
index 00000000000..bda035d8f0f
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_observer.h b/chromium/ui/events/platform/platform_event_observer.h
new file mode 100644
index 00000000000..37aec638ea5
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_source.cc b/chromium/ui/events/platform/platform_event_source.cc
new file mode 100644
index 00000000000..9607ab78232
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_source.h b/chromium/ui/events/platform/platform_event_source.h
new file mode 100644
index 00000000000..87b553197a3
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_source_stub.cc b/chromium/ui/events/platform/platform_event_source_stub.cc
new file mode 100644
index 00000000000..57e2a61c3dc
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_source_unittest.cc b/chromium/ui/events/platform/platform_event_source_unittest.cc
new file mode 100644
index 00000000000..cdbd0c4b35c
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/platform_event_types.h b/chromium/ui/events/platform/platform_event_types.h
new file mode 100644
index 00000000000..dedb38ff063
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/scoped_event_dispatcher.cc b/chromium/ui/events/platform/scoped_event_dispatcher.cc
new file mode 100644
index 00000000000..b0941ab8513
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/scoped_event_dispatcher.h b/chromium/ui/events/platform/scoped_event_dispatcher.h
new file mode 100644
index 00000000000..a2f52945d91
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/x11/BUILD.gn b/chromium/ui/events/platform/x11/BUILD.gn
new file mode 100644
index 00000000000..6c8bbe4b1f2
--- /dev/null
+++ b/chromium/ui/events/platform/x11/BUILD.gn
@@ -0,0 +1,42 @@
+# 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",
+ ]
+
+ deps = [
+ "//ui/events",
+ "//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/chromium/ui/events/platform/x11/x11_event_source.cc b/chromium/ui/events/platform/x11/x11_event_source.cc
new file mode 100644
index 00000000000..25e74098fac
--- /dev/null
+++ b/chromium/ui/events/platform/x11/x11_event_source.cc
@@ -0,0 +1,149 @@
+// 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/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_);
+ InitializeXInput2(display_);
+ InitializeXkb(display_);
+}
+
+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();
+ }
+
+ if (have_cookie)
+ XFreeEventData(xevent->xgeneric.display, &xevent->xcookie);
+ return action;
+}
+
+void X11EventSource::StopCurrentEventStream() {
+ continue_stream_ = false;
+}
+
+} // namespace ui
diff --git a/chromium/ui/events/platform/x11/x11_event_source.h b/chromium/ui/events/platform/x11/x11_event_source.h
new file mode 100644
index 00000000000..602eda67dd3
--- /dev/null
+++ b/chromium/ui/events/platform/x11/x11_event_source.h
@@ -0,0 +1,64 @@
+// 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 {
+
+// 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(X11EventSource);
+};
+
+} // namespace ui
+
+#endif // UI_EVENTS_PLATFORM_X11_X11_EVENT_SOURCE_H_
diff --git a/chromium/ui/events/platform/x11/x11_event_source_glib.cc b/chromium/ui/events/platform/x11/x11_event_source_glib.cc
new file mode 100644
index 00000000000..95044226b50
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/x11/x11_event_source_libevent.cc b/chromium/ui/events/platform/x11/x11_event_source_libevent.cc
new file mode 100644
index 00000000000..d92e12a5d82
--- /dev/null
+++ b/chromium/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/chromium/ui/events/platform/x11/x11_events_platform.gyp b/chromium/ui/events/platform/x11/x11_events_platform.gyp
new file mode 100644
index 00000000000..9070de7cb29
--- /dev/null
+++ b/chromium/ui/events/platform/x11/x11_events_platform.gyp
@@ -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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [{
+ '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_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/chromium/ui/events/win/events_win.cc b/chromium/ui/events/win/events_win.cc
index e255fc988eb..9e74d3a4556 100644
--- a/chromium/ui/events/win/events_win.cc
+++ b/chromium/ui/events/win/events_win.cc
@@ -77,6 +77,11 @@ bool IsNonClientMouseEvent(const base::NativeEvent& native_event) {
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;
@@ -231,7 +236,7 @@ gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
native_point.y = GET_Y_LPARAM(native_event.lParam);
}
ScreenToClient(native_event.hwnd, &native_point);
- return gfx::win::ScreenToDIPPoint(gfx::Point(native_point));
+ return gfx::Point(native_point);
}
gfx::Point EventSystemLocationFromNative(
@@ -250,9 +255,8 @@ const char* CodeFromNative(const base::NativeEvent& native_event) {
return CodeForWindowsScanCode(scan_code);
}
-bool IsMouseEvent(const base::NativeEvent& native_event) {
- return IsClientMouseEvent(native_event) ||
- IsNonClientMouseEvent(native_event);
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ return static_cast<uint32>(native_event.wParam);
}
int GetChangedMouseButtonFlagsFromNative(
@@ -279,6 +283,13 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
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 ClearTouchIdIfReleased(const base::NativeEvent& xev) {
NOTIMPLEMENTED();
}
@@ -355,16 +366,6 @@ bool IsTouchpadEvent(const base::NativeEvent& event) {
return false;
}
-bool IsNoopEvent(const base::NativeEvent& event) {
- return event.message == WM_USER + 310;
-}
-
-base::NativeEvent CreateNoopEvent() {
- MSG event = { NULL };
- event.message = WM_USER + 310;
- return event;
-}
-
int GetModifiersFromACCEL(const ACCEL& accel) {
int modifiers = EF_NONE;
if (accel.fVirt & FSHIFT)
diff --git a/chromium/ui/events/x/device_data_manager.cc b/chromium/ui/events/x/device_data_manager.cc
index a2e02928c4b..4cc0957b4a8 100644
--- a/chromium/ui/events/x/device_data_manager.cc
+++ b/chromium/ui/events/x/device_data_manager.cc
@@ -10,9 +10,13 @@
#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/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
@@ -110,8 +114,7 @@ DeviceDataManager* DeviceDataManager::GetInstance() {
}
DeviceDataManager::DeviceDataManager()
- : natural_scroll_enabled_(false),
- xi_opcode_(-1),
+ : xi_opcode_(-1),
atom_cache_(gfx::GetXDisplay(), kCachedAtoms),
button_map_count_(0) {
CHECK(gfx::GetXDisplay());
@@ -121,6 +124,8 @@ DeviceDataManager::DeviceDataManager()
CHECK(arraysize(kCachedAtoms) == static_cast<size_t>(DT_LAST_ENTRY) + 1);
UpdateDeviceList(gfx::GetXDisplay());
UpdateButtonMap();
+ for (int i = 0; i < kMaxDeviceNum; i++)
+ touch_device_to_display_map_[i] = gfx::Display::kInvalidDisplayID;
}
DeviceDataManager::~DeviceDataManager() {
@@ -177,14 +182,6 @@ bool DeviceDataManager::IsXInput2Available() const {
return xi_opcode_ != -1;
}
-float DeviceDataManager::GetNaturalScrollFactor(int sourceid) const {
- // Natural scroll is touchpad-only.
- if (sourceid >= kMaxDeviceNum || !touchpads_[sourceid])
- return -1.0f;
-
- return natural_scroll_enabled_ ? 1.0f : -1.0f;
-}
-
void DeviceDataManager::UpdateDeviceList(Display* display) {
cmt_devices_.reset();
touchpads_.reset();
@@ -460,20 +457,17 @@ void DeviceDataManager::GetScrollOffsets(const base::NativeEvent& native_event,
*y_offset_ordinal = 0;
*finger_count = 2;
- XIDeviceEvent* xiev =
- static_cast<XIDeviceEvent*>(native_event->xcookie.data);
- const float natural_scroll_factor = GetNaturalScrollFactor(xiev->sourceid);
EventData data;
GetEventRawData(*native_event, &data);
if (data.find(DT_CMT_SCROLL_X) != data.end())
- *x_offset = data[DT_CMT_SCROLL_X] * natural_scroll_factor;
+ *x_offset = data[DT_CMT_SCROLL_X];
if (data.find(DT_CMT_SCROLL_Y) != data.end())
- *y_offset = data[DT_CMT_SCROLL_Y] * natural_scroll_factor;
+ *y_offset = data[DT_CMT_SCROLL_Y];
if (data.find(DT_CMT_ORDINAL_X) != data.end())
- *x_offset_ordinal = data[DT_CMT_ORDINAL_X] * natural_scroll_factor;
+ *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] * natural_scroll_factor;
+ *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]);
}
@@ -488,22 +482,19 @@ void DeviceDataManager::GetFlingData(const base::NativeEvent& native_event,
*vy_ordinal = 0;
*is_cancel = false;
- XIDeviceEvent* xiev =
- static_cast<XIDeviceEvent*>(native_event->xcookie.data);
- const float natural_scroll_factor = GetNaturalScrollFactor(xiev->sourceid);
EventData data;
GetEventRawData(*native_event, &data);
if (data.find(DT_CMT_FLING_X) != data.end())
- *vx = data[DT_CMT_FLING_X] * natural_scroll_factor;
+ *vx = data[DT_CMT_FLING_X];
if (data.find(DT_CMT_FLING_Y) != data.end())
- *vy = data[DT_CMT_FLING_Y] * natural_scroll_factor;
+ *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] * natural_scroll_factor;
+ *vx_ordinal = data[DT_CMT_ORDINAL_X];
if (data.find(DT_CMT_ORDINAL_Y) != data.end())
- *vy_ordinal = data[DT_CMT_ORDINAL_Y] * natural_scroll_factor;
+ *vy_ordinal = data[DT_CMT_ORDINAL_Y];
}
void DeviceDataManager::GetMetricsData(const base::NativeEvent& native_event,
@@ -649,4 +640,54 @@ void DeviceDataManager::InitializeValuatorsForTest(int deviceid,
}
}
+bool DeviceDataManager::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 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;
+ }
+}
+
+bool DeviceDataManager::IsTouchDeviceIdValid(int touch_device_id) const {
+ return (touch_device_id > 0 && touch_device_id < kMaxDeviceNum);
+}
+
+void DeviceDataManager::UpdateTouchInfoForDisplay(
+ int64 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::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 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;
+}
+
} // namespace ui
diff --git a/chromium/ui/events/x/device_data_manager.h b/chromium/ui/events/x/device_data_manager.h
index aff06493571..1135ec222b8 100644
--- a/chromium/ui/events/x/device_data_manager.h
+++ b/chromium/ui/events/x/device_data_manager.h
@@ -20,8 +20,12 @@
#include "base/basictypes.h"
#include "base/event_types.h"
+#include "base/memory/scoped_ptr.h"
+#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/events_base_export.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/transform.h"
#include "ui/gfx/x/x11_atom_cache.h"
template <typename T> struct DefaultSingletonTraits;
@@ -104,18 +108,9 @@ class EVENTS_BASE_EXPORT DeviceDataManager {
// Returns the DeviceDataManager singleton.
static DeviceDataManager* GetInstance();
- // Natural scroll setter/getter.
- bool natural_scroll_enabled() const { return natural_scroll_enabled_; }
- void set_natural_scroll_enabled(bool enabled) {
- natural_scroll_enabled_ = enabled;
- }
-
// Returns if XInput2 is available on the system.
bool IsXInput2Available() const;
- // Get the natural scroll direction multiplier (1.0f or -1.0f).
- float GetNaturalScrollFactor(int sourceid) const;
-
// Updates the list of devices.
void UpdateDeviceList(Display* display);
@@ -226,6 +221,14 @@ class EVENTS_BASE_EXPORT DeviceDataManager {
DataType type,
double value);
+ void ClearTouchTransformerRecord();
+ void UpdateTouchInfoForDisplay(int64 display_id,
+ int touch_device_id,
+ const gfx::Transform& touch_transformer);
+ void ApplyTouchTransformer(int touch_device_id, float* x, float* y);
+ int64 GetDisplayForTouchDevice(int touch_device_id) const;
+ bool TouchEventNeedsCalibrate(int touch_device_id) const;
+
private:
// Requirement for Singleton.
friend struct DefaultSingletonTraits<DeviceDataManager>;
@@ -245,10 +248,11 @@ class EVENTS_BASE_EXPORT DeviceDataManager {
double min_value,
double max_value);
+ bool IsTouchDeviceIdValid(int touch_device_id) const;
+
static const int kMaxDeviceNum = 128;
static const int kMaxXIEventType = XI_LASTEVENT + 1;
static const int kMaxSlotNum = 10;
- bool natural_scroll_enabled_;
// Major opcode for the XInput extension. Used to identify XInput events.
int xi_opcode_;
@@ -292,6 +296,11 @@ class EVENTS_BASE_EXPORT DeviceDataManager {
unsigned char button_map_[256];
int button_map_count_;
+ // Table to keep track of which display id is mapped to which touch device.
+ int64 touch_device_to_display_map_[kMaxDeviceNum];
+ // Index table to find the TouchTransformer for a touch device.
+ gfx::Transform touch_device_transformer_map_[kMaxDeviceNum];
+
DISALLOW_COPY_AND_ASSIGN(DeviceDataManager);
};
diff --git a/chromium/ui/events/x/events_x.cc b/chromium/ui/events/x/events_x.cc
index 5e3b9dd5462..bd12bd835db 100644
--- a/chromium/ui/events/x/events_x.cc
+++ b/chromium/ui/events/x/events_x.cc
@@ -9,10 +9,10 @@
#include <X11/extensions/XInput.h>
#include <X11/extensions/XInput2.h>
#include <X11/Xlib.h>
+#include <X11/Xutil.h>
#include "base/logging.h"
#include "base/memory/singleton.h"
-#include "base/message_loop/message_pump_x11.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/x/device_data_manager.h"
@@ -137,6 +137,10 @@ int GetEventFlagsFromXState(unsigned int state) {
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)
@@ -148,6 +152,36 @@ int GetEventFlagsFromXState(unsigned int state) {
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) |
+ (IsKeypadKey(XLookupKeysym(&xevent->xkey, 0)) ? ui::EF_NUMPAD_KEY : 0) |
+ (IsFunctionKey(XLookupKeysym(&xevent->xkey, 0)) ?
+ ui::EF_FUNCTION_KEY : 0) |
+ ime_fabricated_flag;
+}
+
// 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
@@ -225,10 +259,6 @@ double GetTouchParamFromXEvent(XEvent* xev,
return default_value;
}
-Atom GetNoopEventAtom() {
- return XInternAtom(gfx::GetXDisplay(), "noop", False);
-}
-
} // namespace
namespace ui {
@@ -277,10 +307,19 @@ EventType EventTypeFromNative(const base::NativeEvent& native_event) {
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)
@@ -323,7 +362,7 @@ int EventFlagsFromNative(const base::NativeEvent& native_event) {
case KeyPress:
case KeyRelease: {
XModifierStateWatcher::GetInstance()->UpdateStateFromEvent(native_event);
- return GetEventFlagsFromXState(native_event->xkey.state);
+ return GetEventFlagsFromXKeyEvent(native_event);
}
case ButtonPress:
case ButtonRelease: {
@@ -333,6 +372,9 @@ int EventFlagsFromNative(const base::NativeEvent& native_event) {
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: {
@@ -426,8 +468,21 @@ gfx::Point EventLocationFromNative(const base::NativeEvent& native_event) {
case GenericEvent: {
XIDeviceEvent* xievent =
static_cast<XIDeviceEvent*>(native_event->xcookie.data);
- return gfx::Point(static_cast<int>(xievent->event_x),
- static_cast<int>(xievent->event_y));
+ 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::DeviceDataManager::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();
@@ -478,21 +533,10 @@ const char* CodeFromNative(const base::NativeEvent& native_event) {
return CodeFromXEvent(native_event);
}
-bool IsMouseEvent(const base::NativeEvent& native_event) {
- if (native_event->type == EnterNotify ||
- native_event->type == LeaveNotify ||
- native_event->type == ButtonPress ||
- native_event->type == ButtonRelease ||
- native_event->type == MotionNotify)
- return true;
- if (native_event->type == GenericEvent) {
- XIDeviceEvent* xievent =
- static_cast<XIDeviceEvent*>(native_event->xcookie.data);
- return xievent->evtype == XI_ButtonPress ||
- xievent->evtype == XI_ButtonRelease ||
- xievent->evtype == XI_Motion;
- }
- return false;
+uint32 PlatformKeycodeFromNative(const base::NativeEvent& native_event) {
+ KeySym keysym;
+ XLookupString(&native_event->xkey, NULL, 0, &keysym, NULL);
+ return keysym;
}
int GetChangedMouseButtonFlagsFromNative(
@@ -534,12 +578,27 @@ gfx::Vector2d GetMouseWheelOffset(const base::NativeEvent& native_event) {
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:
- // TODO(derat): Do something for horizontal scrolls (buttons 6 and 7)?
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 ClearTouchIdIfReleased(const base::NativeEvent& xev) {
ui::EventType type = ui::EventTypeFromNative(xev);
if (type == ui::ET_TOUCH_CANCELLED ||
@@ -673,37 +732,8 @@ bool GetGestureTimes(const base::NativeEvent& native_event,
return true;
}
-void SetNaturalScroll(bool enabled) {
- DeviceDataManager::GetInstance()->set_natural_scroll_enabled(enabled);
-}
-
-bool IsNaturalScrollEnabled() {
- return DeviceDataManager::GetInstance()->natural_scroll_enabled();
-}
-
bool IsTouchpadEvent(const base::NativeEvent& event) {
return DeviceDataManager::GetInstance()->IsTouchpadXInputEvent(event);
}
-bool IsNoopEvent(const base::NativeEvent& event) {
- return (event->type == ClientMessage &&
- event->xclient.message_type == GetNoopEventAtom());
-}
-
-base::NativeEvent CreateNoopEvent() {
- static XEvent* noop = NULL;
- if (!noop) {
- noop = new XEvent();
- memset(noop, 0, sizeof(XEvent));
- noop->xclient.type = ClientMessage;
- noop->xclient.window = None;
- noop->xclient.format = 8;
- DCHECK(!noop->xclient.display);
- }
- // Make sure we use atom from current xdisplay, which may
- // change during the test.
- noop->xclient.message_type = GetNoopEventAtom();
- return noop;
-}
-
} // namespace ui
diff --git a/chromium/ui/events/x/events_x_unittest.cc b/chromium/ui/events/x/events_x_unittest.cc
index b6c7f38c0ce..5047c59b19c 100644
--- a/chromium/ui/events/x/events_x_unittest.cc
+++ b/chromium/ui/events/x/events_x_unittest.cc
@@ -6,6 +6,8 @@
#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
@@ -40,6 +42,37 @@ void InitButtonEvent(XEvent* event,
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, false);
+ return (ui_key_event.flags() & ui::EF_FUNCTION_KEY);
+ }
+ return true;
+}
+
} // namespace
TEST(EventsXTest, ButtonEvents) {
@@ -51,7 +84,6 @@ TEST(EventsXTest, ButtonEvents) {
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));
- EXPECT_TRUE(ui::IsMouseEvent(&event));
InitButtonEvent(&event, true, location, 2, Button1Mask | ShiftMask);
EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(&event));
@@ -59,20 +91,17 @@ TEST(EventsXTest, ButtonEvents) {
ui::EF_SHIFT_DOWN,
ui::EventFlagsFromNative(&event));
EXPECT_EQ(location, ui::EventLocationFromNative(&event));
- EXPECT_TRUE(ui::IsMouseEvent(&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));
- EXPECT_TRUE(ui::IsMouseEvent(&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));
- EXPECT_TRUE(ui::IsMouseEvent(&event));
offset = ui::GetMouseWheelOffset(&event);
EXPECT_GT(offset.y(), 0);
EXPECT_EQ(0, offset.x());
@@ -82,30 +111,27 @@ TEST(EventsXTest, ButtonEvents) {
EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(&event));
EXPECT_EQ(0, ui::EventFlagsFromNative(&event));
EXPECT_EQ(location, ui::EventLocationFromNative(&event));
- EXPECT_TRUE(ui::IsMouseEvent(&event));
offset = ui::GetMouseWheelOffset(&event);
EXPECT_LT(offset.y(), 0);
EXPECT_EQ(0, offset.x());
- // Scroll left, typically.
+ // 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));
- EXPECT_TRUE(ui::IsMouseEvent(&event));
offset = ui::GetMouseWheelOffset(&event);
EXPECT_EQ(0, offset.y());
- EXPECT_EQ(0, offset.x());
+ EXPECT_GT(offset.x(), 0);
- // Scroll right, typically.
+ // 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));
- EXPECT_TRUE(ui::IsMouseEvent(&event));
offset = ui::GetMouseWheelOffset(&event);
EXPECT_EQ(0, offset.y());
- EXPECT_EQ(0, offset.x());
+ EXPECT_LT(offset.x(), 0);
// TODO(derat): Test XInput code.
}
@@ -245,4 +271,193 @@ TEST(EventsXTest, TouchEventBasic) {
EXPECT_FLOAT_EQ(GetTouchForce(scoped_xevent), 0.5f);
}
#endif
+
+TEST(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, false);
+ EXPECT_EQ(keys[k].is_numpad_key ? ui::EF_NUMPAD_KEY : 0,
+ ui_key_event.flags() & ui::EF_NUMPAD_KEY);
+ }
+ }
+}
+
+TEST(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(OS_CHROMEOS)
+TEST(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, is_char);
+ 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, is_char);
+ EXPECT_FALSE(key_event.flags() & ui::EF_IME_FABRICATED_KEY);
+ }
+ }
+}
+#endif
+
} // namespace ui
diff --git a/chromium/ui/events/x/touch_factory_x11.cc b/chromium/ui/events/x/touch_factory_x11.cc
index 62f6c89dab7..8176ce221a6 100644
--- a/chromium/ui/events/x/touch_factory_x11.cc
+++ b/chromium/ui/events/x/touch_factory_x11.cc
@@ -4,6 +4,7 @@
#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>
@@ -81,6 +82,7 @@ void TouchFactory::UpdateDeviceList(Display* display) {
touch_device_available_ = false;
touch_device_lookup_.reset();
touch_device_list_.clear();
+ touchscreen_ids_.clear();
max_touch_points_ = -1;
#if !defined(USE_XI2_MT)
@@ -128,7 +130,7 @@ void TouchFactory::UpdateDeviceList(Display* display) {
XIAnyClassInfo* xiclassinfo = devinfo->classes[k];
if (xiclassinfo->type == XITouchClass) {
XITouchClassInfo* tci =
- reinterpret_cast<XITouchClassInfo *>(xiclassinfo);
+ 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;
@@ -142,6 +144,21 @@ void TouchFactory::UpdateDeviceList(Display* display) {
#endif
pointer_device_lookup_[devinfo->deviceid] = true;
}
+
+#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
}
}
@@ -268,4 +285,43 @@ void TouchFactory::SetPointerDeviceForTest(
}
}
+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/chromium/ui/events/x/touch_factory_x11.h b/chromium/ui/events/x/touch_factory_x11.h
index ed62c6d247f..4d8a989fd8f 100644
--- a/chromium/ui/events/x/touch_factory_x11.h
+++ b/chromium/ui/events/x/touch_factory_x11.h
@@ -7,6 +7,8 @@
#include <bitset>
#include <map>
+#include <set>
+#include <utility>
#include <vector>
#include "base/timer/timer.h"
@@ -72,6 +74,11 @@ class EVENTS_BASE_EXPORT TouchFactory {
// 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;
@@ -89,6 +96,8 @@ class EVENTS_BASE_EXPORT TouchFactory {
// 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,
@@ -118,6 +127,9 @@ class EVENTS_BASE_EXPORT TouchFactory {
// capable.
std::map<int, bool> touch_device_list_;
+ // Touch screen <vid, pid>s.
+ std::set<std::pair<int, int> > touchscreen_ids_;
+
// 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