1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/renderer_host/input/touch_action_filter.h" 6 7 #include <math.h> 8 9 #include "base/logging.h" 10 #include "third_party/WebKit/public/web/WebInputEvent.h" 11 12 using blink::WebInputEvent; 13 using blink::WebGestureEvent; 14 15 namespace content { 16 17 TouchActionFilter::TouchActionFilter() : 18 drop_scroll_gesture_events_(false), 19 drop_pinch_gesture_events_(false), 20 drop_current_tap_ending_event_(false), 21 allow_current_double_tap_event_(true), 22 allowed_touch_action_(TOUCH_ACTION_AUTO) { 23 } 24 25 bool TouchActionFilter::FilterGestureEvent(WebGestureEvent* gesture_event) { 26 // Filter for allowable touch actions first (eg. before the TouchEventQueue 27 // can decide to send a touch cancel event). 28 switch(gesture_event->type) { 29 case WebInputEvent::GestureScrollBegin: 30 DCHECK(!drop_scroll_gesture_events_); 31 drop_scroll_gesture_events_ = ShouldSuppressScroll(*gesture_event); 32 return drop_scroll_gesture_events_; 33 34 case WebInputEvent::GestureScrollUpdate: 35 if (drop_scroll_gesture_events_) 36 return true; 37 else { 38 if (allowed_touch_action_ == TOUCH_ACTION_PAN_X) { 39 gesture_event->data.scrollUpdate.deltaY = 0; 40 gesture_event->data.scrollUpdate.velocityY = 0; 41 } else if (allowed_touch_action_ == TOUCH_ACTION_PAN_Y) { 42 gesture_event->data.scrollUpdate.deltaX = 0; 43 gesture_event->data.scrollUpdate.velocityX = 0; 44 } 45 } 46 break; 47 48 case WebInputEvent::GestureFlingStart: 49 if (gesture_event->sourceDevice != blink::WebGestureDeviceTouchscreen) 50 break; 51 if (!drop_scroll_gesture_events_) { 52 if (allowed_touch_action_ == TOUCH_ACTION_PAN_X) 53 gesture_event->data.flingStart.velocityY = 0; 54 if (allowed_touch_action_ == TOUCH_ACTION_PAN_Y) 55 gesture_event->data.flingStart.velocityX = 0; 56 } 57 return FilterScrollEndingGesture(); 58 59 case WebInputEvent::GestureScrollEnd: 60 return FilterScrollEndingGesture(); 61 62 case WebInputEvent::GesturePinchBegin: 63 DCHECK(!drop_pinch_gesture_events_); 64 if (allowed_touch_action_ == TOUCH_ACTION_AUTO || 65 allowed_touch_action_ & TOUCH_ACTION_PINCH_ZOOM) { 66 // Pinch events are always bracketed by scroll events, and the W3C 67 // standard touch-action provides no way to disable scrolling without 68 // also disabling pinching (validated by the IPC ENUM traits). 69 DCHECK(allowed_touch_action_ == TOUCH_ACTION_AUTO || 70 allowed_touch_action_ == TOUCH_ACTION_MANIPULATION); 71 DCHECK(!drop_scroll_gesture_events_); 72 } else { 73 drop_pinch_gesture_events_ = true; 74 } 75 return drop_pinch_gesture_events_; 76 77 case WebInputEvent::GesturePinchUpdate: 78 return drop_pinch_gesture_events_; 79 80 case WebInputEvent::GesturePinchEnd: 81 if (drop_pinch_gesture_events_) { 82 drop_pinch_gesture_events_ = false; 83 return true; 84 } 85 DCHECK(!drop_scroll_gesture_events_); 86 break; 87 88 // The double tap gesture is a tap ending event. If a double tap gesture is 89 // filtered out, replace it with a tap event. 90 case WebInputEvent::GestureDoubleTap: 91 DCHECK_EQ(1, gesture_event->data.tap.tapCount); 92 if (!allow_current_double_tap_event_) 93 gesture_event->type = WebInputEvent::GestureTap; 94 allow_current_double_tap_event_ = true; 95 break; 96 97 // If double tap is disabled, there's no reason for the tap delay. 98 case WebInputEvent::GestureTapUnconfirmed: 99 DCHECK_EQ(1, gesture_event->data.tap.tapCount); 100 allow_current_double_tap_event_ = 101 allowed_touch_action_ == TOUCH_ACTION_AUTO; 102 if (!allow_current_double_tap_event_) { 103 gesture_event->type = WebInputEvent::GestureTap; 104 drop_current_tap_ending_event_ = true; 105 } 106 break; 107 108 case WebInputEvent::GestureTap: 109 allow_current_double_tap_event_ = 110 allowed_touch_action_ == TOUCH_ACTION_AUTO; 111 // Fall through. 112 case WebInputEvent::GestureTapCancel: 113 if (drop_current_tap_ending_event_) { 114 drop_current_tap_ending_event_ = false; 115 return true; 116 } 117 break; 118 119 case WebInputEvent::GestureTapDown: 120 DCHECK(!drop_current_tap_ending_event_); 121 break; 122 123 default: 124 // Gesture events unrelated to touch actions (panning/zooming) are left 125 // alone. 126 break; 127 } 128 129 return false; 130 } 131 132 bool TouchActionFilter::FilterScrollEndingGesture() { 133 DCHECK(!drop_pinch_gesture_events_); 134 if (drop_scroll_gesture_events_) { 135 drop_scroll_gesture_events_ = false; 136 return true; 137 } 138 return false; 139 } 140 141 void TouchActionFilter::OnSetTouchAction(TouchAction touch_action) { 142 // For multiple fingers, we take the intersection of the touch actions for 143 // all fingers that have gone down during this action. In the majority of 144 // real-world scenarios the touch action for all fingers will be the same. 145 // This is left as implementation-defined in the pointer events 146 // specification because of the relationship to gestures (which are off 147 // limits for the spec). I believe the following are desirable properties 148 // of this choice: 149 // 1. Not sensitive to finger touch order. Behavior of putting two fingers 150 // down "at once" will be deterministic. 151 // 2. Only subtractive - eg. can't trigger scrolling on a element that 152 // otherwise has scrolling disabling by the addition of a finger. 153 allowed_touch_action_ = Intersect(allowed_touch_action_, touch_action); 154 } 155 156 void TouchActionFilter::ResetTouchAction() { 157 // Note that resetting the action mid-sequence is tolerated. Gestures that had 158 // their begin event(s) suppressed will be suppressed until the next sequence. 159 allowed_touch_action_ = TOUCH_ACTION_AUTO; 160 } 161 162 bool TouchActionFilter::ShouldSuppressScroll( 163 const blink::WebGestureEvent& gesture_event) { 164 DCHECK_EQ(gesture_event.type, WebInputEvent::GestureScrollBegin); 165 if (allowed_touch_action_ == TOUCH_ACTION_AUTO) 166 return false; 167 if (allowed_touch_action_ == TOUCH_ACTION_NONE) 168 return true; 169 170 // If there's no hint or it's perfectly diagonal, then allow the scroll. 171 if (fabs(gesture_event.data.scrollBegin.deltaXHint) == 172 fabs(gesture_event.data.scrollBegin.deltaYHint)) 173 return false; 174 175 // Determine the primary initial axis of the scroll, and check whether 176 // panning along that axis is permitted. 177 if (fabs(gesture_event.data.scrollBegin.deltaXHint) > 178 fabs(gesture_event.data.scrollBegin.deltaYHint)) 179 return !(allowed_touch_action_ & TOUCH_ACTION_PAN_X); 180 return !(allowed_touch_action_ & TOUCH_ACTION_PAN_Y); 181 } 182 183 TouchAction TouchActionFilter::Intersect(TouchAction ta1, TouchAction ta2) { 184 if (ta1 == TOUCH_ACTION_NONE || ta2 == TOUCH_ACTION_NONE) 185 return TOUCH_ACTION_NONE; 186 if (ta1 == TOUCH_ACTION_AUTO) 187 return ta2; 188 if (ta2 == TOUCH_ACTION_AUTO) 189 return ta1; 190 191 // Only the true flags are left - take their intersection. 192 if (!(ta1 & ta2)) 193 return TOUCH_ACTION_NONE; 194 return static_cast<TouchAction>(ta1 & ta2); 195 } 196 197 } 198