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/gesture_event_filter.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/strings/string_number_conversions.h"
      9 #include "content/browser/renderer_host/input/input_router.h"
     10 #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
     11 #include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
     12 #include "content/public/common/content_switches.h"
     13 
     14 using blink::WebGestureEvent;
     15 using blink::WebInputEvent;
     16 
     17 namespace content {
     18 namespace {
     19 
     20 // Default debouncing interval duration: if a scroll is in progress, non-scroll
     21 // events during this interval are deferred to either its end or discarded on
     22 // receipt of another GestureScrollUpdate.
     23 static const int kDebouncingIntervalTimeMs = 30;
     24 
     25 }  // namespace
     26 
     27 GestureEventFilter::GestureEventFilter(
     28     GestureEventFilterClient* client,
     29     TouchpadTapSuppressionControllerClient* touchpad_client)
     30      : client_(client),
     31        fling_in_progress_(false),
     32        scrolling_in_progress_(false),
     33        ignore_next_ack_(false),
     34        combined_scroll_pinch_(gfx::Transform()),
     35        touchpad_tap_suppression_controller_(
     36            new TouchpadTapSuppressionController(touchpad_client)),
     37        touchscreen_tap_suppression_controller_(
     38            new TouchscreenTapSuppressionController(this)),
     39        debounce_interval_time_ms_(kDebouncingIntervalTimeMs),
     40        debounce_enabled_(true) {
     41   DCHECK(client);
     42   DCHECK(touchpad_tap_suppression_controller_);
     43   if (CommandLine::ForCurrentProcess()->HasSwitch(
     44           switches::kDisableGestureDebounce)) {
     45     debounce_enabled_ = false;
     46   }
     47 }
     48 
     49 GestureEventFilter::~GestureEventFilter() { }
     50 
     51 bool GestureEventFilter::ShouldDiscardFlingCancelEvent(
     52     const GestureEventWithLatencyInfo& gesture_event) const {
     53   if (coalesced_gesture_events_.empty() && fling_in_progress_)
     54     return false;
     55   GestureEventQueue::const_reverse_iterator it =
     56       coalesced_gesture_events_.rbegin();
     57   while (it != coalesced_gesture_events_.rend()) {
     58     if (it->event.type == WebInputEvent::GestureFlingStart)
     59       return false;
     60     if (it->event.type == WebInputEvent::GestureFlingCancel)
     61       return true;
     62     it++;
     63   }
     64   return true;
     65 }
     66 
     67 bool GestureEventFilter::ShouldForwardForBounceReduction(
     68     const GestureEventWithLatencyInfo& gesture_event) {
     69   if (!debounce_enabled_)
     70     return true;
     71   switch (gesture_event.event.type) {
     72     case WebInputEvent::GestureScrollUpdate:
     73       if (!scrolling_in_progress_) {
     74         debounce_deferring_timer_.Start(
     75             FROM_HERE,
     76             base::TimeDelta::FromMilliseconds(debounce_interval_time_ms_),
     77             this,
     78             &GestureEventFilter::SendScrollEndingEventsNow);
     79       } else {
     80         // Extend the bounce interval.
     81         debounce_deferring_timer_.Reset();
     82       }
     83       scrolling_in_progress_ = true;
     84       debouncing_deferral_queue_.clear();
     85       return true;
     86     case WebInputEvent::GesturePinchBegin:
     87     case WebInputEvent::GesturePinchEnd:
     88     case WebInputEvent::GesturePinchUpdate:
     89       // TODO(rjkroege): Debounce pinch (http://crbug.com/147647)
     90       return true;
     91     default:
     92       if (scrolling_in_progress_) {
     93         debouncing_deferral_queue_.push_back(gesture_event);
     94         return false;
     95       }
     96       return true;
     97   }
     98 
     99   NOTREACHED();
    100   return false;
    101 }
    102 
    103 // NOTE: The filters are applied successively. This simplifies the change.
    104 bool GestureEventFilter::ShouldForward(
    105     const GestureEventWithLatencyInfo& gesture_event) {
    106   return ShouldForwardForZeroVelocityFlingStart(gesture_event) &&
    107       ShouldForwardForBounceReduction(gesture_event) &&
    108       ShouldForwardForGFCFiltering(gesture_event) &&
    109       ShouldForwardForTapSuppression(gesture_event) &&
    110       ShouldForwardForCoalescing(gesture_event);
    111 }
    112 
    113 bool GestureEventFilter::ShouldForwardForZeroVelocityFlingStart(
    114     const GestureEventWithLatencyInfo& gesture_event) const {
    115   return gesture_event.event.type != WebInputEvent::GestureFlingStart ||
    116       gesture_event.event.sourceDevice != WebGestureEvent::Touchpad ||
    117       gesture_event.event.data.flingStart.velocityX != 0 ||
    118       gesture_event.event.data.flingStart.velocityY != 0;
    119 }
    120 
    121 bool GestureEventFilter::ShouldForwardForGFCFiltering(
    122     const GestureEventWithLatencyInfo& gesture_event) const {
    123   return gesture_event.event.type != WebInputEvent::GestureFlingCancel ||
    124       !ShouldDiscardFlingCancelEvent(gesture_event);
    125 }
    126 
    127 bool GestureEventFilter::ShouldForwardForTapSuppression(
    128     const GestureEventWithLatencyInfo& gesture_event) {
    129   switch (gesture_event.event.type) {
    130     case WebInputEvent::GestureFlingCancel:
    131       if (gesture_event.event.sourceDevice == WebGestureEvent::Touchscreen)
    132         touchscreen_tap_suppression_controller_->GestureFlingCancel();
    133       else
    134         touchpad_tap_suppression_controller_->GestureFlingCancel();
    135       return true;
    136     case WebInputEvent::GestureTapDown:
    137       return !touchscreen_tap_suppression_controller_->
    138           ShouldDeferGestureTapDown(gesture_event);
    139     case WebInputEvent::GestureShowPress:
    140       return !touchscreen_tap_suppression_controller_->
    141           ShouldDeferGestureShowPress(gesture_event);
    142     case WebInputEvent::GestureTapCancel:
    143     case WebInputEvent::GestureTap:
    144     case WebInputEvent::GestureTapUnconfirmed:
    145     case WebInputEvent::GestureDoubleTap:
    146       return !touchscreen_tap_suppression_controller_->
    147           ShouldSuppressGestureTapEnd();
    148     default:
    149       return true;
    150   }
    151   NOTREACHED();
    152   return false;
    153 }
    154 
    155 bool GestureEventFilter::ShouldForwardForCoalescing(
    156     const GestureEventWithLatencyInfo& gesture_event) {
    157   switch (gesture_event.event.type) {
    158     case WebInputEvent::GestureFlingCancel:
    159       fling_in_progress_ = false;
    160       break;
    161     case WebInputEvent::GestureFlingStart:
    162       fling_in_progress_ = true;
    163       break;
    164     case WebInputEvent::GesturePinchUpdate:
    165     case WebInputEvent::GestureScrollUpdate:
    166       MergeOrInsertScrollAndPinchEvent(gesture_event);
    167       return ShouldHandleEventNow();
    168     default:
    169       break;
    170   }
    171   EnqueueEvent(gesture_event);
    172   return ShouldHandleEventNow();
    173 }
    174 
    175 void GestureEventFilter::ProcessGestureAck(InputEventAckState ack_result,
    176                                            WebInputEvent::Type type,
    177                                            const ui::LatencyInfo& latency) {
    178   if (coalesced_gesture_events_.empty()) {
    179     DLOG(ERROR) << "Received unexpected ACK for event type " << type;
    180     return;
    181   }
    182 
    183   // Ack'ing an event may enqueue additional gesture events.  By ack'ing the
    184   // event before the forwarding of queued events below, such additional events
    185   // can be coalesced with existing queued events prior to dispatch.
    186   GestureEventWithLatencyInfo event_with_latency =
    187       coalesced_gesture_events_.front();
    188   DCHECK_EQ(event_with_latency.event.type, type);
    189   event_with_latency.latency.AddNewLatencyFrom(latency);
    190   client_->OnGestureEventAck(event_with_latency, ack_result);
    191 
    192   const bool processed = (INPUT_EVENT_ACK_STATE_CONSUMED == ack_result);
    193   if (type == WebInputEvent::GestureFlingCancel) {
    194     if (event_with_latency.event.sourceDevice == WebGestureEvent::Touchscreen)
    195       touchscreen_tap_suppression_controller_->GestureFlingCancelAck(processed);
    196     else
    197       touchpad_tap_suppression_controller_->GestureFlingCancelAck(processed);
    198   }
    199   coalesced_gesture_events_.pop_front();
    200 
    201   if (ignore_next_ack_) {
    202     ignore_next_ack_ = false;
    203     return;
    204   }
    205 
    206   if (coalesced_gesture_events_.empty())
    207     return;
    208 
    209   const GestureEventWithLatencyInfo& first_gesture_event =
    210       coalesced_gesture_events_.front();
    211 
    212   // TODO(yusufo): Introduce GesturePanScroll so that these can be combined
    213   // into one gesture and kept inside the queue that way.
    214   // Check for the coupled GesturePinchUpdate before sending either event,
    215   // handling the case where the first GestureScrollUpdate ack is synchronous.
    216   GestureEventWithLatencyInfo second_gesture_event;
    217   if (first_gesture_event.event.type == WebInputEvent::GestureScrollUpdate &&
    218       coalesced_gesture_events_.size() > 1 &&
    219       coalesced_gesture_events_[1].event.type ==
    220           WebInputEvent::GesturePinchUpdate) {
    221     second_gesture_event = coalesced_gesture_events_[1];
    222     ignore_next_ack_ = true;
    223   }
    224 
    225   client_->SendGestureEventImmediately(first_gesture_event);
    226   if (second_gesture_event.event.type != WebInputEvent::Undefined)
    227     client_->SendGestureEventImmediately(second_gesture_event);
    228 }
    229 
    230 TouchpadTapSuppressionController*
    231     GestureEventFilter::GetTouchpadTapSuppressionController() {
    232   return touchpad_tap_suppression_controller_.get();
    233 }
    234 
    235 bool GestureEventFilter::HasQueuedGestureEvents() const {
    236   return !coalesced_gesture_events_.empty();
    237 }
    238 
    239 void GestureEventFilter::FlingHasBeenHalted() {
    240   fling_in_progress_ = false;
    241 }
    242 
    243 bool GestureEventFilter::ShouldHandleEventNow() const {
    244   return coalesced_gesture_events_.size() == 1;
    245 }
    246 
    247 void GestureEventFilter::ForwardGestureEvent(
    248     const GestureEventWithLatencyInfo& gesture_event) {
    249   if (ShouldForwardForCoalescing(gesture_event))
    250     client_->SendGestureEventImmediately(gesture_event);
    251 }
    252 
    253 void GestureEventFilter::SendScrollEndingEventsNow() {
    254   scrolling_in_progress_ = false;
    255   GestureEventQueue debouncing_deferral_queue;
    256   debouncing_deferral_queue.swap(debouncing_deferral_queue_);
    257   for (GestureEventQueue::const_iterator it = debouncing_deferral_queue.begin();
    258        it != debouncing_deferral_queue.end(); it++) {
    259     if (ShouldForwardForGFCFiltering(*it) &&
    260         ShouldForwardForTapSuppression(*it) &&
    261         ShouldForwardForCoalescing(*it)) {
    262       client_->SendGestureEventImmediately(*it);
    263     }
    264   }
    265 }
    266 
    267 void GestureEventFilter::MergeOrInsertScrollAndPinchEvent(
    268     const GestureEventWithLatencyInfo& gesture_event) {
    269   if (coalesced_gesture_events_.size() <= 1) {
    270     EnqueueEvent(gesture_event);
    271     return;
    272   }
    273   GestureEventWithLatencyInfo* last_event = &coalesced_gesture_events_.back();
    274   if (last_event->CanCoalesceWith(gesture_event)) {
    275     last_event->CoalesceWith(gesture_event);
    276     if (!combined_scroll_pinch_.IsIdentity()) {
    277       combined_scroll_pinch_.ConcatTransform(
    278           GetTransformForEvent(gesture_event));
    279     }
    280     return;
    281   }
    282   if (coalesced_gesture_events_.size() == 2 ||
    283       (coalesced_gesture_events_.size() == 3 && ignore_next_ack_) ||
    284       !ShouldTryMerging(gesture_event, *last_event)) {
    285     EnqueueEvent(gesture_event);
    286     return;
    287   }
    288   GestureEventWithLatencyInfo scroll_event;
    289   GestureEventWithLatencyInfo pinch_event;
    290   scroll_event.event.modifiers |= gesture_event.event.modifiers;
    291   scroll_event.event.timeStampSeconds = gesture_event.event.timeStampSeconds;
    292   // Keep the oldest LatencyInfo.
    293   DCHECK_LE(last_event->latency.trace_id, gesture_event.latency.trace_id);
    294   scroll_event.latency = last_event->latency;
    295   pinch_event = scroll_event;
    296   scroll_event.event.type = WebInputEvent::GestureScrollUpdate;
    297   pinch_event.event.type = WebInputEvent::GesturePinchUpdate;
    298   pinch_event.event.x = gesture_event.event.type ==
    299       WebInputEvent::GesturePinchUpdate ?
    300           gesture_event.event.x : last_event->event.x;
    301   pinch_event.event.y = gesture_event.event.type ==
    302       WebInputEvent::GesturePinchUpdate ?
    303           gesture_event.event.y : last_event->event.y;
    304 
    305   combined_scroll_pinch_.ConcatTransform(GetTransformForEvent(gesture_event));
    306   GestureEventWithLatencyInfo* second_last_event = &coalesced_gesture_events_
    307       [coalesced_gesture_events_.size() - 2];
    308   if (ShouldTryMerging(gesture_event, *second_last_event)) {
    309     // Keep the oldest LatencyInfo.
    310     DCHECK_LE(second_last_event->latency.trace_id,
    311               scroll_event.latency.trace_id);
    312     scroll_event.latency = second_last_event->latency;
    313     pinch_event.latency = second_last_event->latency;
    314     coalesced_gesture_events_.pop_back();
    315   } else {
    316     DCHECK(combined_scroll_pinch_ == GetTransformForEvent(gesture_event));
    317     combined_scroll_pinch_.
    318         PreconcatTransform(GetTransformForEvent(*last_event));
    319   }
    320   coalesced_gesture_events_.pop_back();
    321   float combined_scale =
    322       SkMScalarToFloat(combined_scroll_pinch_.matrix().get(0, 0));
    323   float combined_scroll_pinch_x =
    324       SkMScalarToFloat(combined_scroll_pinch_.matrix().get(0, 3));
    325   float combined_scroll_pinch_y =
    326       SkMScalarToFloat(combined_scroll_pinch_.matrix().get(1, 3));
    327   scroll_event.event.data.scrollUpdate.deltaX =
    328       (combined_scroll_pinch_x + pinch_event.event.x) / combined_scale -
    329       pinch_event.event.x;
    330   scroll_event.event.data.scrollUpdate.deltaY =
    331       (combined_scroll_pinch_y + pinch_event.event.y) / combined_scale -
    332       pinch_event.event.y;
    333   coalesced_gesture_events_.push_back(scroll_event);
    334   pinch_event.event.data.pinchUpdate.scale = combined_scale;
    335   coalesced_gesture_events_.push_back(pinch_event);
    336 }
    337 
    338 bool GestureEventFilter::ShouldTryMerging(
    339     const GestureEventWithLatencyInfo& new_event,
    340     const GestureEventWithLatencyInfo& event_in_queue) const {
    341   DLOG_IF(WARNING,
    342           new_event.event.timeStampSeconds <
    343           event_in_queue.event.timeStampSeconds)
    344           << "Event time not monotonic?\n";
    345   return (event_in_queue.event.type == WebInputEvent::GestureScrollUpdate ||
    346       event_in_queue.event.type == WebInputEvent::GesturePinchUpdate) &&
    347       event_in_queue.event.modifiers == new_event.event.modifiers;
    348 }
    349 
    350 gfx::Transform GestureEventFilter::GetTransformForEvent(
    351     const GestureEventWithLatencyInfo& gesture_event) const {
    352   gfx::Transform gesture_transform = gfx::Transform();
    353   if (gesture_event.event.type == WebInputEvent::GestureScrollUpdate) {
    354     gesture_transform.Translate(gesture_event.event.data.scrollUpdate.deltaX,
    355                                 gesture_event.event.data.scrollUpdate.deltaY);
    356   } else if (gesture_event.event.type == WebInputEvent::GesturePinchUpdate) {
    357     float scale = gesture_event.event.data.pinchUpdate.scale;
    358     gesture_transform.Translate(-gesture_event.event.x, -gesture_event.event.y);
    359     gesture_transform.Scale(scale,scale);
    360     gesture_transform.Translate(gesture_event.event.x, gesture_event.event.y);
    361   }
    362   return gesture_transform;
    363 }
    364 
    365 void GestureEventFilter::EnqueueEvent(
    366     const GestureEventWithLatencyInfo& gesture_event) {
    367   coalesced_gesture_events_.push_back(gesture_event);
    368   // Scroll and pinch events contributing to |combined_scroll_pinch_| will be
    369   // manually added to the queue in |MergeOrInsertScrollAndPinchEvent()|.
    370   combined_scroll_pinch_ = gfx::Transform();
    371 }
    372 
    373 }  // namespace content
    374