Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2013 The Chromium Authors |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Kartar Singh | d084e58 | 2024-06-12 19:22:24 | [diff] [blame] | 5 | #include "components/input/touch_action_filter.h" |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 6 | |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 7 | #include <math.h> |
| 8 | |
Hans Wennborg | 0917de89 | 2020-04-28 20:21:15 | [diff] [blame] | 9 | #include "base/check_op.h" |
Hans Wennborg | 0917de89 | 2020-04-28 20:21:15 | [diff] [blame] | 10 | #include "base/notreached.h" |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 11 | #include "base/trace_event/trace_event.h" |
Dave Tapuska | 129cef8 | 2019-12-19 16:36:48 | [diff] [blame] | 12 | #include "third_party/blink/public/common/input/web_gesture_event.h" |
Michelle | 9e74c52f | 2023-06-02 10:21:34 | [diff] [blame] | 13 | #include "ui/base/ui_base_features.h" |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 14 | #include "ui/events/blink/blink_features.h" |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 15 | |
| 16 | using blink::WebInputEvent; |
| 17 | using blink::WebGestureEvent; |
| 18 | |
Kartar Singh | d084e58 | 2024-06-12 19:22:24 | [diff] [blame] | 19 | namespace input { |
dtapuska | a98ac8d7 | 2015-05-08 19:29:09 | [diff] [blame] | 20 | namespace { |
| 21 | |
| 22 | // Actions on an axis are disallowed if the perpendicular axis has a filter set |
| 23 | // and no filter is set for the queried axis. |
xidachen | fa0199e7 | 2017-05-11 11:34:26 | [diff] [blame] | 24 | bool IsYAxisActionDisallowed(cc::TouchAction action) { |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 25 | return ((action & cc::TouchAction::kPanX) != cc::TouchAction::kNone) && |
| 26 | ((action & cc::TouchAction::kPanY) == cc::TouchAction::kNone); |
dtapuska | a98ac8d7 | 2015-05-08 19:29:09 | [diff] [blame] | 27 | } |
| 28 | |
xidachen | fa0199e7 | 2017-05-11 11:34:26 | [diff] [blame] | 29 | bool IsXAxisActionDisallowed(cc::TouchAction action) { |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 30 | return ((action & cc::TouchAction::kPanY) != cc::TouchAction::kNone) && |
| 31 | ((action & cc::TouchAction::kPanX) == cc::TouchAction::kNone); |
dtapuska | a98ac8d7 | 2015-05-08 19:29:09 | [diff] [blame] | 32 | } |
| 33 | |
Shimi Zhang | 9287e2cd | 2020-10-31 00:40:35 | [diff] [blame] | 34 | void SetCursorControlIfNecessary(WebGestureEvent* event, |
| 35 | cc::TouchAction action) { |
| 36 | if (event->data.scroll_begin.pointer_count != 1) |
| 37 | return; |
| 38 | const float abs_delta_x = fabs(event->data.scroll_begin.delta_x_hint); |
| 39 | const float abs_delta_y = fabs(event->data.scroll_begin.delta_y_hint); |
| 40 | if (abs_delta_x <= abs_delta_y) |
| 41 | return; |
| 42 | |
| 43 | // We shouldn't reach here if kPanX is not allowed for horizontal scroll. |
| 44 | DCHECK_NE(action & cc::TouchAction::kPanX, cc::TouchAction::kNone); |
| 45 | if ((action & cc::TouchAction::kInternalPanXScrolls) == |
| 46 | cc::TouchAction::kInternalPanXScrolls) |
| 47 | return; |
| 48 | |
| 49 | event->data.scroll_begin.cursor_control = true; |
| 50 | } |
| 51 | |
dtapuska | a98ac8d7 | 2015-05-08 19:29:09 | [diff] [blame] | 52 | } // namespace |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 53 | |
David Bokan | c8973d4 | 2020-04-07 05:58:09 | [diff] [blame] | 54 | TouchActionFilter::TouchActionFilter() { |
Xida Chen | 0d326b4 | 2018-08-22 20:39:26 | [diff] [blame] | 55 | ResetTouchAction(); |
| 56 | } |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 57 | |
Sorin Jianu | 5842190 | 2024-10-10 21:33:45 | [diff] [blame] | 58 | TouchActionFilter::~TouchActionFilter() = default; |
Xida Chen | ee78df3 | 2018-08-03 17:40:40 | [diff] [blame] | 59 | |
Xida Chen | e051a6c | 2018-05-02 16:35:13 | [diff] [blame] | 60 | FilterGestureEventResult TouchActionFilter::FilterGestureEvent( |
| 61 | WebGestureEvent* gesture_event) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 62 | TRACE_EVENT0("input", "TouchActionFilter::FilterGestureEvent"); |
Daniel Cheng | 7f9ec90 | 2019-04-18 05:07:00 | [diff] [blame] | 63 | if (gesture_event->SourceDevice() != blink::WebGestureDevice::kTouchscreen) |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 64 | return FilterGestureEventResult::kAllowed; |
dtapuska | 3a5f4db | 2016-05-12 20:53:49 | [diff] [blame] | 65 | |
David Bokan | c8973d4 | 2020-04-07 05:58:09 | [diff] [blame] | 66 | if (has_deferred_events_) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 67 | TRACE_EVENT_INSTANT0("input", "Has Deferred", TRACE_EVENT_SCOPE_THREAD); |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 68 | return FilterGestureEventResult::kDelayed; |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 69 | } |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 70 | |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 71 | TRACE_EVENT_INSTANT1( |
| 72 | "input", "active_action", TRACE_EVENT_SCOPE_THREAD, "action", |
| 73 | (active_touch_action_.has_value() |
| 74 | ? cc::TouchActionToString(active_touch_action_.value()) |
| 75 | : "n/a")); |
| 76 | TRACE_EVENT_INSTANT1( |
| 77 | "input", "allowed_action", TRACE_EVENT_SCOPE_THREAD, "action", |
| 78 | (allowed_touch_action_.has_value() |
| 79 | ? cc::TouchActionToString(allowed_touch_action_.value()) |
| 80 | : "n/a")); |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 81 | TRACE_EVENT_INSTANT1( |
| 82 | "input", "compositor_allowed_action", TRACE_EVENT_SCOPE_THREAD, "action", |
| 83 | cc::TouchActionToString(compositor_allowed_touch_action_)); |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 84 | |
Xida Chen | 77aa9e7 | 2019-02-14 02:01:32 | [diff] [blame] | 85 | cc::TouchAction touch_action = active_touch_action_.has_value() |
| 86 | ? active_touch_action_.value() |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 87 | : compositor_allowed_touch_action_; |
Xida Chen | 84d0845e | 2018-11-02 20:36:54 | [diff] [blame] | 88 | |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 89 | // Filter for allowable touch actions first (eg. before the TouchEventQueue |
| 90 | // can decide to send a touch cancel event). |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 91 | switch (gesture_event->GetType()) { |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 92 | case WebInputEvent::Type::kGestureScrollBegin: { |
Xida Chen | 166974ea | 2018-09-10 21:02:35 | [diff] [blame] | 93 | // In VR or virtual keyboard (https://crbug.com/880701), |
| 94 | // GestureScrollBegin could come without GestureTapDown. |
David Bokan | f22d6160 | 2020-04-06 21:54:37 | [diff] [blame] | 95 | // TODO(bokan): This can also happen due to the fling controller |
| 96 | // filtering out the GestureTapDown due to tap suppression (i.e. tapping |
| 97 | // during a fling should stop the fling, not be sent to the page). We |
| 98 | // should not reset the touch action in this case! We currently work |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 99 | // around this by resetting the compositor allowed touch action in this |
| 100 | // case as well but we should investigate not filtering the TapDown. |
Xida Chen | 4563099 | 2018-08-29 19:54:20 | [diff] [blame] | 101 | if (!gesture_sequence_in_progress_) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 102 | TRACE_EVENT_INSTANT0("input", "No Sequence at GSB!", |
| 103 | TRACE_EVENT_SCOPE_THREAD); |
Xida Chen | 4563099 | 2018-08-29 19:54:20 | [diff] [blame] | 104 | gesture_sequence_in_progress_ = true; |
Xida Chen | 77aa9e7 | 2019-02-14 02:01:32 | [diff] [blame] | 105 | if (allowed_touch_action_.has_value()) { |
| 106 | active_touch_action_ = allowed_touch_action_; |
| 107 | touch_action = allowed_touch_action_.value(); |
| 108 | } else { |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 109 | touch_action = compositor_allowed_touch_action_; |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 110 | } |
Xida Chen | 4563099 | 2018-08-29 19:54:20 | [diff] [blame] | 111 | } |
Shimi Zhang | 9287e2cd | 2020-10-31 00:40:35 | [diff] [blame] | 112 | drop_scroll_events_ = ShouldSuppressScrolling( |
| 113 | *gesture_event, touch_action, active_touch_action_.has_value()); |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 114 | FilterGestureEventResult res; |
| 115 | if (!drop_scroll_events_) { |
Michelle | 55a2439 | 2023-05-19 01:39:37 | [diff] [blame] | 116 | if (allow_cursor_control_) { |
| 117 | SetCursorControlIfNecessary(gesture_event, touch_action); |
| 118 | } |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 119 | res = FilterGestureEventResult::kAllowed; |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 120 | } else if (active_touch_action_.has_value()) { |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 121 | res = FilterGestureEventResult::kFiltered; |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 122 | } else { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 123 | TRACE_EVENT_INSTANT0("input", "Deferring Events", |
| 124 | TRACE_EVENT_SCOPE_THREAD); |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 125 | has_deferred_events_ = true; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 126 | res = FilterGestureEventResult::kDelayed; |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 127 | } |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 128 | return res; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 129 | } |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 130 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 131 | case WebInputEvent::Type::kGestureScrollUpdate: { |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 132 | if (drop_scroll_events_) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 133 | TRACE_EVENT_INSTANT0("input", "Drop Events", TRACE_EVENT_SCOPE_THREAD); |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 134 | return FilterGestureEventResult::kFiltered; |
Xida Chen | 36070799 | 2018-11-20 14:25:51 | [diff] [blame] | 135 | } |
mustaq | 16ce460 | 2017-03-14 16:22:36 | [diff] [blame] | 136 | |
| 137 | // Scrolls restricted to a specific axis shouldn't permit movement |
| 138 | // in the perpendicular axis. |
| 139 | // |
| 140 | // Note the direction suppression with pinch-zoom here, which matches |
| 141 | // Edge: a "touch-action: pan-y pinch-zoom" region allows vertical |
| 142 | // two-finger scrolling but a "touch-action: pan-x pinch-zoom" region |
| 143 | // doesn't. |
| 144 | // TODO(mustaq): Add it to spec? |
Xida Chen | 77aa9e7 | 2019-02-14 02:01:32 | [diff] [blame] | 145 | if (IsYAxisActionDisallowed(touch_action)) { |
David Bokan | c8973d4 | 2020-04-07 05:58:09 | [diff] [blame] | 146 | if (!active_touch_action_.has_value() && |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 147 | gesture_event->data.scroll_update.delta_y != 0) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 148 | TRACE_EVENT_INSTANT0("input", "Defer Due to YAxis", |
| 149 | TRACE_EVENT_SCOPE_THREAD); |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 150 | has_deferred_events_ = true; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 151 | return FilterGestureEventResult::kDelayed; |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 152 | } |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 153 | gesture_event->data.scroll_update.delta_y = 0; |
Xida Chen | 77aa9e7 | 2019-02-14 02:01:32 | [diff] [blame] | 154 | } else if (IsXAxisActionDisallowed(touch_action)) { |
David Bokan | c8973d4 | 2020-04-07 05:58:09 | [diff] [blame] | 155 | if (!active_touch_action_.has_value() && |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 156 | gesture_event->data.scroll_update.delta_x != 0) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 157 | TRACE_EVENT_INSTANT0("input", "Defer Due to XAxis", |
| 158 | TRACE_EVENT_SCOPE_THREAD); |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 159 | has_deferred_events_ = true; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 160 | return FilterGestureEventResult::kDelayed; |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 161 | } |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 162 | gesture_event->data.scroll_update.delta_x = 0; |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 163 | } |
| 164 | break; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 165 | } |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 166 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 167 | case WebInputEvent::Type::kGestureFlingStart: |
Xida Chen | ce09732e | 2018-05-30 15:22:48 | [diff] [blame] | 168 | // Fling controller processes FlingStart event, and we should never get |
| 169 | // it here. |
Peter Boström | 0af5ffa1 | 2024-11-08 11:47:04 | [diff] [blame] | 170 | NOTREACHED(); |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 171 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 172 | case WebInputEvent::Type::kGestureScrollEnd: |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 173 | // Do not reset |compositor_allowed_touch_action_|. In the fling cancel |
| 174 | // case, the ack for the second touch sequence start, which sets the |
| 175 | // compositor allowed touch action, could arrive before the GSE of the |
| 176 | // first fling sequence, we do not want to reset the compositor allowed |
| 177 | // touch action. |
Xida Chen | 7a7759f | 2018-08-22 16:42:31 | [diff] [blame] | 178 | gesture_sequence_in_progress_ = false; |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 179 | return FilterScrollEventAndResetState(); |
[email protected] | aec5f6f | 2014-01-03 17:41:58 | [diff] [blame] | 180 | |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 181 | // Evaluate the |drop_pinch_events_| here instead of GSB because pinch |
| 182 | // events could arrive without GSB, e.g. double-tap-drag. |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 183 | case WebInputEvent::Type::kGesturePinchBegin: |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 184 | drop_pinch_events_ = (touch_action & cc::TouchAction::kPinchZoom) == |
| 185 | cc::TouchAction::kNone; |
Roland Bock | 1556a48 | 2022-01-04 16:28:40 | [diff] [blame] | 186 | [[fallthrough]]; |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 187 | case WebInputEvent::Type::kGesturePinchUpdate: |
Xida Chen | 9776276 | 2019-02-21 02:51:14 | [diff] [blame] | 188 | if (!drop_pinch_events_) |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 189 | return FilterGestureEventResult::kAllowed; |
David Bokan | c8973d4 | 2020-04-07 05:58:09 | [diff] [blame] | 190 | if (!active_touch_action_.has_value()) { |
Xida Chen | 9776276 | 2019-02-21 02:51:14 | [diff] [blame] | 191 | has_deferred_events_ = true; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 192 | return FilterGestureEventResult::kDelayed; |
Xida Chen | 9776276 | 2019-02-21 02:51:14 | [diff] [blame] | 193 | } |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 194 | return FilterGestureEventResult::kFiltered; |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 195 | case WebInputEvent::Type::kGesturePinchEnd: |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 196 | return FilterPinchEventAndResetState(); |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 197 | |
Mustaq Ahmed | 70a68cf8 | 2018-10-11 20:58:20 | [diff] [blame] | 198 | // The double tap gesture is a tap ending event. If a double-tap gesture is |
| 199 | // filtered out, replace it with a tap event but preserve the tap-count to |
| 200 | // allow firing dblclick event in Blink. |
| 201 | // |
| 202 | // TODO(mustaq): This replacement of a double-tap gesture with a tap seems |
| 203 | // buggy, it produces an inconsistent gesture event stream: GestureTapCancel |
| 204 | // followed by GestureTap. See crbug.com/874474#c47 for a repro. We don't |
| 205 | // know of any bug resulting from it, but it's better to fix the broken |
| 206 | // assumption here at least to avoid introducing new bugs in future. |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 207 | case WebInputEvent::Type::kGestureDoubleTap: |
Xida Chen | 7a7759f | 2018-08-22 16:42:31 | [diff] [blame] | 208 | gesture_sequence_in_progress_ = false; |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 209 | DCHECK_EQ(1, gesture_event->data.tap.tap_count); |
Mustaq Ahmed | 70a68cf8 | 2018-10-11 20:58:20 | [diff] [blame] | 210 | if (!allow_current_double_tap_event_) { |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 211 | gesture_event->SetType(WebInputEvent::Type::kGestureTap); |
Mustaq Ahmed | 70a68cf8 | 2018-10-11 20:58:20 | [diff] [blame] | 212 | gesture_event->data.tap.tap_count = 2; |
| 213 | } |
[email protected] | 3571a33e | 2014-03-04 22:18:02 | [diff] [blame] | 214 | allow_current_double_tap_event_ = true; |
[email protected] | 0783edd | 2014-02-19 20:48:20 | [diff] [blame] | 215 | break; |
| 216 | |
| 217 | // If double tap is disabled, there's no reason for the tap delay. |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 218 | case WebInputEvent::Type::kGestureTapUnconfirmed: { |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 219 | DCHECK_EQ(1, gesture_event->data.tap.tap_count); |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 220 | allow_current_double_tap_event_ = |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 221 | (touch_action & cc::TouchAction::kDoubleTapZoom) != |
| 222 | cc::TouchAction::kNone; |
[email protected] | 3571a33e | 2014-03-04 22:18:02 | [diff] [blame] | 223 | if (!allow_current_double_tap_event_) { |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 224 | gesture_event->SetType(WebInputEvent::Type::kGestureTap); |
[email protected] | 0783edd | 2014-02-19 20:48:20 | [diff] [blame] | 225 | drop_current_tap_ending_event_ = true; |
| 226 | } |
| 227 | break; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 228 | } |
[email protected] | 0783edd | 2014-02-19 20:48:20 | [diff] [blame] | 229 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 230 | case WebInputEvent::Type::kGestureTap: |
Xida Chen | 7a7759f | 2018-08-22 16:42:31 | [diff] [blame] | 231 | gesture_sequence_in_progress_ = false; |
[email protected] | 0783edd | 2014-02-19 20:48:20 | [diff] [blame] | 232 | if (drop_current_tap_ending_event_) { |
| 233 | drop_current_tap_ending_event_ = false; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 234 | return FilterGestureEventResult::kFiltered; |
[email protected] | 0783edd | 2014-02-19 20:48:20 | [diff] [blame] | 235 | } |
| 236 | break; |
| 237 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 238 | case WebInputEvent::Type::kGestureTapCancel: |
Xida Chen | b9001ac | 2018-08-27 18:57:50 | [diff] [blame] | 239 | if (drop_current_tap_ending_event_) { |
| 240 | drop_current_tap_ending_event_ = false; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 241 | return FilterGestureEventResult::kFiltered; |
Xida Chen | b9001ac | 2018-08-27 18:57:50 | [diff] [blame] | 242 | } |
| 243 | break; |
| 244 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 245 | case WebInputEvent::Type::kGestureTapDown: |
Xida Chen | 7a7759f | 2018-08-22 16:42:31 | [diff] [blame] | 246 | gesture_sequence_in_progress_ = true; |
Michelle | 9e74c52f | 2023-06-02 10:21:34 | [diff] [blame] | 247 | allow_cursor_control_ = |
| 248 | !::features::IsTouchTextEditingRedesignEnabled() || |
| 249 | gesture_event->data.tap_down.tap_down_count <= 1; |
Xida Chen | e1460a2f | 2018-11-02 15:30:20 | [diff] [blame] | 250 | // In theory, the num_of_active_touches_ should be > 0 at this point. But |
| 251 | // crash reports suggest otherwise. |
Xida Chen | 84d0845e | 2018-11-02 20:36:54 | [diff] [blame] | 252 | if (num_of_active_touches_ <= 0) |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 253 | SetTouchAction(cc::TouchAction::kAuto); |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 254 | active_touch_action_ = allowed_touch_action_; |
[email protected] | 0783edd | 2014-02-19 20:48:20 | [diff] [blame] | 255 | DCHECK(!drop_current_tap_ending_event_); |
| 256 | break; |
| 257 | |
Michelle | 55a2439 | 2023-05-19 01:39:37 | [diff] [blame] | 258 | case WebInputEvent::Type::kGestureLongPress: |
| 259 | allow_cursor_control_ = false; |
| 260 | break; |
| 261 | |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 262 | case WebInputEvent::Type::kGestureLongTap: |
| 263 | case WebInputEvent::Type::kGestureTwoFingerTap: |
Xida Chen | b9001ac | 2018-08-27 18:57:50 | [diff] [blame] | 264 | gesture_sequence_in_progress_ = false; |
| 265 | break; |
| 266 | |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 267 | default: |
| 268 | // Gesture events unrelated to touch actions (panning/zooming) are left |
| 269 | // alone. |
| 270 | break; |
| 271 | } |
| 272 | |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 273 | return FilterGestureEventResult::kAllowed; |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 274 | } |
| 275 | |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 276 | void TouchActionFilter::SetTouchAction(cc::TouchAction touch_action) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 277 | TRACE_EVENT1("input", "TouchActionFilter::SetTouchAction", "action", |
| 278 | cc::TouchActionToString(touch_action)); |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 279 | allowed_touch_action_ = touch_action; |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 280 | active_touch_action_ = allowed_touch_action_; |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 281 | compositor_allowed_touch_action_ = touch_action; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 282 | } |
| 283 | |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 284 | FilterGestureEventResult TouchActionFilter::FilterPinchEventAndResetState() { |
| 285 | if (drop_pinch_events_) { |
| 286 | drop_pinch_events_ = false; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 287 | return FilterGestureEventResult::kFiltered; |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 288 | } |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 289 | return FilterGestureEventResult::kAllowed; |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 290 | } |
| 291 | |
| 292 | FilterGestureEventResult TouchActionFilter::FilterScrollEventAndResetState() { |
| 293 | if (drop_scroll_events_) { |
| 294 | drop_scroll_events_ = false; |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 295 | return FilterGestureEventResult::kFiltered; |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 296 | } |
Kartar Singh | 3a96886 | 2024-07-19 11:46:10 | [diff] [blame] | 297 | return FilterGestureEventResult::kAllowed; |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 298 | } |
| 299 | |
Xida Chen | 0f7980b | 2018-10-12 12:40:53 | [diff] [blame] | 300 | void TouchActionFilter::ForceResetTouchActionForTest() { |
| 301 | allowed_touch_action_.reset(); |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 302 | active_touch_action_.reset(); |
Xida Chen | 0f7980b | 2018-10-12 12:40:53 | [diff] [blame] | 303 | } |
| 304 | |
xidachen | fa0199e7 | 2017-05-11 11:34:26 | [diff] [blame] | 305 | void TouchActionFilter::OnSetTouchAction(cc::TouchAction touch_action) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 306 | TRACE_EVENT2("input", "TouchActionFilter::OnSetTouchAction", "action", |
| 307 | cc::TouchActionToString(touch_action), "allowed", |
| 308 | (allowed_touch_action_.has_value() |
| 309 | ? cc::TouchActionToString(allowed_touch_action_.value()) |
| 310 | : "n/a")); |
Alison Gale | 53c77f6 | 2024-04-22 15:16:27 | [diff] [blame] | 311 | // TODO(crbug.com/40579429): add a DCHECK for |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 312 | // |has_touch_event_handler_|. |
[email protected] | 5d0bbdfa9 | 2013-12-10 00:35:51 | [diff] [blame] | 313 | // For multiple fingers, we take the intersection of the touch actions for |
[email protected] | aec5f6f | 2014-01-03 17:41:58 | [diff] [blame] | 314 | // all fingers that have gone down during this action. In the majority of |
| 315 | // real-world scenarios the touch action for all fingers will be the same. |
| 316 | // This is left as implementation-defined in the pointer events |
| 317 | // specification because of the relationship to gestures (which are off |
| 318 | // limits for the spec). I believe the following are desirable properties |
| 319 | // of this choice: |
| 320 | // 1. Not sensitive to finger touch order. Behavior of putting two fingers |
| 321 | // down "at once" will be deterministic. |
| 322 | // 2. Only subtractive - eg. can't trigger scrolling on a element that |
| 323 | // otherwise has scrolling disabling by the addition of a finger. |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 324 | allowed_touch_action_ = |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 325 | allowed_touch_action_.value_or(cc::TouchAction::kAuto) & touch_action; |
chaopeng | e5f829bb | 2017-08-25 00:35:26 | [diff] [blame] | 326 | |
| 327 | // When user enabled force enable zoom, we should always allow pinch-zoom |
| 328 | // except for touch-action:none. |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 329 | if (force_enable_zoom_ && allowed_touch_action_ != cc::TouchAction::kNone) { |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 330 | allowed_touch_action_ = |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 331 | allowed_touch_action_.value() | cc::TouchAction::kPinchZoom; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 332 | } |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 333 | active_touch_action_ = allowed_touch_action_; |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 334 | has_deferred_events_ = false; |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 335 | } |
| 336 | |
Xida Chen | e1460a2f | 2018-11-02 15:30:20 | [diff] [blame] | 337 | void TouchActionFilter::IncreaseActiveTouches() { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 338 | TRACE_EVENT1("input", "TouchActionFilter::IncreaseActiveTouches", "num", |
| 339 | num_of_active_touches_); |
Xida Chen | e1460a2f | 2018-11-02 15:30:20 | [diff] [blame] | 340 | num_of_active_touches_++; |
| 341 | } |
| 342 | |
| 343 | void TouchActionFilter::DecreaseActiveTouches() { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 344 | TRACE_EVENT1("input", "TouchActionFilter::DecreaseActiveTouches", "num", |
| 345 | num_of_active_touches_); |
Xida Chen | e1460a2f | 2018-11-02 15:30:20 | [diff] [blame] | 346 | num_of_active_touches_--; |
Xida Chen | 3811c0a | 2018-08-29 20:01:50 | [diff] [blame] | 347 | } |
| 348 | |
calamity | 0c2262ad | 2018-05-21 03:40:16 | [diff] [blame] | 349 | void TouchActionFilter::ReportAndResetTouchAction() { |
Michelle | 55a2439 | 2023-05-19 01:39:37 | [diff] [blame] | 350 | if (num_of_active_touches_ <= 0) { |
Xida Chen | e1460a2f | 2018-11-02 15:30:20 | [diff] [blame] | 351 | ResetTouchAction(); |
Michelle | 55a2439 | 2023-05-19 01:39:37 | [diff] [blame] | 352 | allow_cursor_control_ = true; |
| 353 | } |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 354 | } |
| 355 | |
[email protected] | bfd4f6e | 2014-02-27 16:27:55 | [diff] [blame] | 356 | void TouchActionFilter::ResetTouchAction() { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 357 | TRACE_EVENT0("input", "TouchActionFilter::ResetTouchAction"); |
[email protected] | 40981da | 2014-05-22 05:22:44 | [diff] [blame] | 358 | // Note that resetting the action mid-sequence is tolerated. Gestures that had |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 359 | // their begin event(s) suppressed will be suppressed until the next sequence. |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 360 | if (has_touch_event_handler_) { |
| 361 | allowed_touch_action_.reset(); |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 362 | compositor_allowed_touch_action_ = cc::TouchAction::kAuto; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 363 | } else { |
| 364 | // Lack of a touch handler indicates that the page either has no |
| 365 | // touch-action modifiers or that all its touch-action modifiers are auto. |
| 366 | // Resetting the touch-action here allows forwarding of subsequent gestures |
| 367 | // even if the underlying touches never reach the router. |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 368 | SetTouchAction(cc::TouchAction::kAuto); |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 369 | } |
Hayley Ferr | aa29bf6 | 2017-07-14 01:40:59 | [diff] [blame] | 370 | } |
| 371 | |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 372 | void TouchActionFilter::OnSetCompositorAllowedTouchAction( |
| 373 | cc::TouchAction allowed_touch_action) { |
| 374 | TRACE_EVENT2("input", "TouchActionFilter::OnSetCompositorAllowedTouchAction", |
| 375 | "action", cc::TouchActionToString(allowed_touch_action), |
| 376 | "current", cc::TouchActionToString(allowed_touch_action)); |
Xida Chen | d7eb636 | 2018-04-05 15:08:21 | [diff] [blame] | 377 | // We use '&' here to account for the multiple-finger case, which is the same |
| 378 | // as OnSetTouchAction. |
Philip Rogers | a2c7377 | 2020-06-22 14:54:13 | [diff] [blame] | 379 | compositor_allowed_touch_action_ = |
| 380 | compositor_allowed_touch_action_ & allowed_touch_action; |
[email protected] | bfd4f6e | 2014-02-27 16:27:55 | [diff] [blame] | 381 | } |
| 382 | |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 383 | bool TouchActionFilter::ShouldSuppressScrolling( |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 384 | const blink::WebGestureEvent& gesture_event, |
Shimi Zhang | 9287e2cd | 2020-10-31 00:40:35 | [diff] [blame] | 385 | cc::TouchAction touch_action, |
| 386 | bool is_active_touch_action) { |
Dave Tapuska | b5a72eb | 2020-04-21 22:12:56 | [diff] [blame] | 387 | DCHECK(gesture_event.GetType() == WebInputEvent::Type::kGestureScrollBegin); |
Shimi Zhang | 9287e2cd | 2020-10-31 00:40:35 | [diff] [blame] | 388 | // If kInternalPanXScrolls is true, kPanX must be true; |
| 389 | DCHECK((touch_action & cc::TouchAction::kInternalPanXScrolls) == |
| 390 | cc::TouchAction::kNone || |
| 391 | (touch_action & cc::TouchAction::kPanX) != cc::TouchAction::kNone); |
mustaq | 16ce460 | 2017-03-14 16:22:36 | [diff] [blame] | 392 | |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 393 | if (gesture_event.data.scroll_begin.pointer_count >= 2) { |
mustaq | 16ce460 | 2017-03-14 16:22:36 | [diff] [blame] | 394 | // Any GestureScrollBegin with more than one fingers is like a pinch-zoom |
| 395 | // for touch-actions, see crbug.com/632525. Therefore, we switch to |
| 396 | // blocked-manipulation mode iff pinch-zoom is disallowed. |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 397 | return (touch_action & cc::TouchAction::kPinchZoom) == |
| 398 | cc::TouchAction::kNone; |
dtapuska | 0e9eadf | 2016-09-27 15:14:10 | [diff] [blame] | 399 | } |
| 400 | |
Blink Reformat | 1c4d759e | 2017-04-09 16:34:54 | [diff] [blame] | 401 | const float& deltaXHint = gesture_event.data.scroll_begin.delta_x_hint; |
| 402 | const float& deltaYHint = gesture_event.data.scroll_begin.delta_y_hint; |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 403 | |
| 404 | if (deltaXHint == 0.0 && deltaYHint == 0.0) |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 405 | return false; |
dtapuska | 0e9eadf | 2016-09-27 15:14:10 | [diff] [blame] | 406 | |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 407 | const float absDeltaXHint = fabs(deltaXHint); |
| 408 | const float absDeltaYHint = fabs(deltaYHint); |
[email protected] | a18f67a | 2013-12-20 19:44:36 | [diff] [blame] | 409 | |
Mahesh Machavolu | 5cf127f | 2022-06-17 09:26:24 | [diff] [blame] | 410 | // We need to wait for main-thread touch action to see if touch region is |
| 411 | // writable for stylus handwriting, and accumulate scroll events until then. |
Mahesh Machavolu | 6cb8018 | 2022-07-22 08:09:31 | [diff] [blame] | 412 | if ((gesture_event.primary_pointer_type == |
| 413 | blink::WebPointerProperties::PointerType::kPen || |
| 414 | gesture_event.primary_pointer_type == |
| 415 | blink::WebPointerProperties::PointerType::kEraser) && |
Mahesh Machavolu | 5cf127f | 2022-06-17 09:26:24 | [diff] [blame] | 416 | !is_active_touch_action && |
| 417 | (touch_action & cc::TouchAction::kInternalNotWritable) != |
| 418 | cc::TouchAction::kInternalNotWritable) |
| 419 | return true; |
| 420 | |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 421 | cc::TouchAction minimal_conforming_touch_action = cc::TouchAction::kNone; |
Shimi Zhang | 9287e2cd | 2020-10-31 00:40:35 | [diff] [blame] | 422 | if (absDeltaXHint > absDeltaYHint) { |
| 423 | // If we're performing a horizontal gesture over a region that could |
| 424 | // potentially activate cursor control, we need to wait for the real |
| 425 | // main-thread touch action before making a decision since we'll need to set |
| 426 | // the cursor control bit correctly. |
| 427 | if (!is_active_touch_action && |
| 428 | (touch_action & cc::TouchAction::kInternalPanXScrolls) != |
| 429 | cc::TouchAction::kInternalPanXScrolls) |
| 430 | return true; |
| 431 | |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 432 | if (deltaXHint > 0) |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 433 | minimal_conforming_touch_action |= cc::TouchAction::kPanLeft; |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 434 | else if (deltaXHint < 0) |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 435 | minimal_conforming_touch_action |= cc::TouchAction::kPanRight; |
Shimi Zhang | 9287e2cd | 2020-10-31 00:40:35 | [diff] [blame] | 436 | } else { |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 437 | if (deltaYHint > 0) |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 438 | minimal_conforming_touch_action |= cc::TouchAction::kPanUp; |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 439 | else if (deltaYHint < 0) |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 440 | minimal_conforming_touch_action |= cc::TouchAction::kPanDown; |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 441 | } |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 442 | DCHECK(minimal_conforming_touch_action != cc::TouchAction::kNone); |
mustaq | 4d73d6e | 2017-03-15 17:54:38 | [diff] [blame] | 443 | |
Henrique Ferreiro | 00e24f1 | 2019-12-17 01:14:48 | [diff] [blame] | 444 | return (touch_action & minimal_conforming_touch_action) == |
| 445 | cc::TouchAction::kNone; |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 446 | } |
| 447 | |
| 448 | void TouchActionFilter::OnHasTouchEventHandlers(bool has_handlers) { |
David Bokan | d0c973dd | 2020-04-09 19:36:36 | [diff] [blame] | 449 | TRACE_EVENT1("input", "TouchActionFilter::OnHasTouchEventHandlers", |
| 450 | "has handlers", has_handlers); |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 451 | // The has_touch_event_handler_ is default to false which is why we have the |
| 452 | // "&&" condition here, to ensure that touch actions will be set if there is |
Sahir Vellani | 0e6d8555 | 2020-08-25 19:35:30 | [diff] [blame] | 453 | // no touch event consumers. |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 454 | if (has_handlers && has_touch_event_handler_ == has_handlers) |
| 455 | return; |
| 456 | has_touch_event_handler_ = has_handlers; |
Xida Chen | 3811c0a | 2018-08-29 20:01:50 | [diff] [blame] | 457 | // We have set the associated touch action if the touch start already happened |
| 458 | // or there is a gesture in progress. In these cases, we should not reset the |
| 459 | // associated touch action. |
Xida Chen | e1460a2f | 2018-11-02 15:30:20 | [diff] [blame] | 460 | if (!gesture_sequence_in_progress_ && num_of_active_touches_ <= 0) { |
Xida Chen | 3811c0a | 2018-08-29 20:01:50 | [diff] [blame] | 461 | ResetTouchAction(); |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 462 | if (has_touch_event_handler_) { |
Xida Chen | 06a0442 | 2018-10-30 05:35:30 | [diff] [blame] | 463 | active_touch_action_.reset(); |
Xida Chen | f164239c | 2018-11-13 01:59:04 | [diff] [blame] | 464 | } |
Xida Chen | 3811c0a | 2018-08-29 20:01:50 | [diff] [blame] | 465 | } |
Xida Chen | 2eec314f | 2018-06-07 11:42:42 | [diff] [blame] | 466 | } |
| 467 | |
Kartar Singh | d084e58 | 2024-06-12 19:22:24 | [diff] [blame] | 468 | } // namespace input |