Home | History | Annotate | Download | only in input
      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