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/renderer/input/input_handler_proxy.h"
6
7#include "base/auto_reset.h"
8#include "base/command_line.h"
9#include "base/debug/trace_event.h"
10#include "base/logging.h"
11#include "base/metrics/histogram.h"
12#include "content/common/input/did_overscroll_params.h"
13#include "content/common/input/web_input_event_traits.h"
14#include "content/public/common/content_switches.h"
15#include "content/renderer/input/input_handler_proxy_client.h"
16#include "third_party/WebKit/public/platform/Platform.h"
17#include "third_party/WebKit/public/web/WebInputEvent.h"
18#include "ui/events/latency_info.h"
19#include "ui/gfx/frame_time.h"
20#include "ui/gfx/geometry/point_conversions.h"
21
22using blink::WebFloatPoint;
23using blink::WebFloatSize;
24using blink::WebGestureEvent;
25using blink::WebInputEvent;
26using blink::WebMouseEvent;
27using blink::WebMouseWheelEvent;
28using blink::WebPoint;
29using blink::WebTouchEvent;
30using blink::WebTouchPoint;
31
32namespace {
33
34// Maximum time between a fling event's timestamp and the first |Animate| call
35// for the fling curve to use the fling timestamp as the initial animation time.
36// Two frames allows a minor delay between event creation and the first animate.
37const double kMaxSecondsFromFlingTimestampToFirstAnimate = 2. / 60.;
38
39// Threshold for determining whether a fling scroll delta should have caused the
40// client to scroll.
41const float kScrollEpsilon = 0.1f;
42
43// Minimum fling velocity required for the active fling and new fling for the
44// two to accumulate.
45const double kMinBoostFlingSpeedSquare = 350. * 350.;
46
47// Minimum velocity for the active touch scroll to preserve (boost) an active
48// fling for which cancellation has been deferred.
49const double kMinBoostTouchScrollSpeedSquare = 150 * 150.;
50
51// Timeout window after which the active fling will be cancelled if no scrolls
52// or flings of sufficient velocity relative to the current fling are received.
53// The default value on Android native views is 40ms, but we use a slightly
54// increased value to accomodate small IPC message delays.
55const double kFlingBoostTimeoutDelaySeconds = 0.045;
56
57gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) {
58  return gfx::Vector2dF(-increment.width, -increment.height);
59}
60
61double InSecondsF(const base::TimeTicks& time) {
62  return (time - base::TimeTicks()).InSecondsF();
63}
64
65bool ShouldSuppressScrollForFlingBoosting(
66    const gfx::Vector2dF& current_fling_velocity,
67    const WebGestureEvent& scroll_update_event,
68    double time_since_last_boost_event) {
69  DCHECK_EQ(WebInputEvent::GestureScrollUpdate, scroll_update_event.type);
70
71  gfx::Vector2dF dx(scroll_update_event.data.scrollUpdate.deltaX,
72                    scroll_update_event.data.scrollUpdate.deltaY);
73  if (gfx::DotProduct(current_fling_velocity, dx) <= 0)
74    return false;
75
76  if (time_since_last_boost_event < 0.001)
77    return true;
78
79  // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
80  // The scroll must be of sufficient velocity to maintain the active fling.
81  const gfx::Vector2dF scroll_velocity =
82      gfx::ScaleVector2d(dx, 1. / time_since_last_boost_event);
83  if (scroll_velocity.LengthSquared() < kMinBoostTouchScrollSpeedSquare)
84    return false;
85
86  return true;
87}
88
89bool ShouldBoostFling(const gfx::Vector2dF& current_fling_velocity,
90                      const WebGestureEvent& fling_start_event) {
91  DCHECK_EQ(WebInputEvent::GestureFlingStart, fling_start_event.type);
92
93  gfx::Vector2dF new_fling_velocity(
94      fling_start_event.data.flingStart.velocityX,
95      fling_start_event.data.flingStart.velocityY);
96
97  if (gfx::DotProduct(current_fling_velocity, new_fling_velocity) <= 0)
98    return false;
99
100  if (current_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
101    return false;
102
103  if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
104    return false;
105
106  return true;
107}
108
109WebGestureEvent ObtainGestureScrollBegin(const WebGestureEvent& event) {
110  WebGestureEvent scroll_begin_event = event;
111  scroll_begin_event.type = WebInputEvent::GestureScrollBegin;
112  scroll_begin_event.data.scrollBegin.deltaXHint = 0;
113  scroll_begin_event.data.scrollBegin.deltaYHint = 0;
114  return scroll_begin_event;
115}
116
117void SendScrollLatencyUma(const WebInputEvent& event,
118                          const ui::LatencyInfo& latency_info) {
119  if (!(event.type == WebInputEvent::GestureScrollBegin ||
120        event.type == WebInputEvent::GestureScrollUpdate ||
121        event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation))
122    return;
123
124  ui::LatencyInfo::LatencyMap::const_iterator it =
125      latency_info.latency_components.find(std::make_pair(
126          ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0));
127
128  if (it == latency_info.latency_components.end())
129    return;
130
131  base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time;
132  for (size_t i = 0; i < it->second.event_count; ++i) {
133    UMA_HISTOGRAM_CUSTOM_COUNTS(
134        "Event.Latency.RendererImpl.GestureScroll2",
135        delta.InMicroseconds(),
136        1,
137        1000000,
138        100);
139  }
140}  // namespace
141
142}
143
144namespace content {
145
146InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler,
147                                     InputHandlerProxyClient* client)
148    : client_(client),
149      input_handler_(input_handler),
150      deferred_fling_cancel_time_seconds_(0),
151#ifndef NDEBUG
152      expect_scroll_update_end_(false),
153#endif
154      gesture_scroll_on_impl_thread_(false),
155      gesture_pinch_on_impl_thread_(false),
156      fling_may_be_active_on_main_thread_(false),
157      disallow_horizontal_fling_scroll_(false),
158      disallow_vertical_fling_scroll_(false),
159      has_fling_animation_started_(false) {
160  DCHECK(client);
161  input_handler_->BindToClient(this);
162  smooth_scroll_enabled_ = CommandLine::ForCurrentProcess()->HasSwitch(
163      switches::kEnableSmoothScrolling);
164}
165
166InputHandlerProxy::~InputHandlerProxy() {}
167
168void InputHandlerProxy::WillShutdown() {
169  input_handler_ = NULL;
170  client_->WillShutdown();
171}
172
173InputHandlerProxy::EventDisposition
174InputHandlerProxy::HandleInputEventWithLatencyInfo(
175    const WebInputEvent& event,
176    ui::LatencyInfo* latency_info) {
177  DCHECK(input_handler_);
178
179  SendScrollLatencyUma(event, *latency_info);
180
181  TRACE_EVENT_FLOW_STEP0("input",
182                         "LatencyInfo.Flow",
183                         TRACE_ID_DONT_MANGLE(latency_info->trace_id),
184                         "HandleInputEventImpl");
185
186  scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
187      input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info);
188  InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event);
189  return disposition;
190}
191
192InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent(
193    const WebInputEvent& event) {
194  DCHECK(input_handler_);
195  TRACE_EVENT1("input", "InputHandlerProxy::HandleInputEvent",
196               "type", WebInputEventTraits::GetName(event.type));
197
198  client_->DidReceiveInputEvent();
199  if (FilterInputEventForFlingBoosting(event))
200    return DID_HANDLE;
201
202  if (event.type == WebInputEvent::MouseWheel) {
203    const WebMouseWheelEvent& wheel_event =
204        *static_cast<const WebMouseWheelEvent*>(&event);
205    if (wheel_event.scrollByPage) {
206      // TODO(jamesr): We don't properly handle scroll by page in the compositor
207      // thread, so punt it to the main thread. http://crbug.com/236639
208      return DID_NOT_HANDLE;
209    }
210    if (wheel_event.modifiers & WebInputEvent::ControlKey) {
211      // Wheel events involving the control key never trigger scrolling, only
212      // event handlers.  Forward to the main thread.
213      return DID_NOT_HANDLE;
214    }
215    if (smooth_scroll_enabled_) {
216      cc::InputHandler::ScrollStatus scroll_status =
217          input_handler_->ScrollAnimated(
218              gfx::Point(wheel_event.x, wheel_event.y),
219              gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY));
220      switch (scroll_status) {
221        case cc::InputHandler::ScrollStarted:
222          return DID_HANDLE;
223        case cc::InputHandler::ScrollIgnored:
224          return DROP_EVENT;
225        default:
226          return DID_NOT_HANDLE;
227      }
228    }
229    cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
230        gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel);
231    switch (scroll_status) {
232      case cc::InputHandler::ScrollStarted: {
233        TRACE_EVENT_INSTANT2(
234            "input",
235            "InputHandlerProxy::handle_input wheel scroll",
236            TRACE_EVENT_SCOPE_THREAD,
237            "deltaX",
238            -wheel_event.deltaX,
239            "deltaY",
240            -wheel_event.deltaY);
241        bool did_scroll = input_handler_->ScrollBy(
242            gfx::Point(wheel_event.x, wheel_event.y),
243            gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY));
244        input_handler_->ScrollEnd();
245        return did_scroll ? DID_HANDLE : DROP_EVENT;
246      }
247      case cc::InputHandler::ScrollIgnored:
248        // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
249        // to properly sync scrollability it's safer to send the event to the
250        // main thread. Change back to DROP_EVENT once we have synchronization
251        // bugs sorted out.
252        return DID_NOT_HANDLE;
253      case cc::InputHandler::ScrollUnknown:
254      case cc::InputHandler::ScrollOnMainThread:
255        return DID_NOT_HANDLE;
256      case cc::InputHandler::ScrollStatusCount:
257        NOTREACHED();
258        break;
259    }
260  } else if (event.type == WebInputEvent::GestureScrollBegin) {
261    DCHECK(!gesture_scroll_on_impl_thread_);
262#ifndef NDEBUG
263    DCHECK(!expect_scroll_update_end_);
264    expect_scroll_update_end_ = true;
265#endif
266    const WebGestureEvent& gesture_event =
267        *static_cast<const WebGestureEvent*>(&event);
268    cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
269        gfx::Point(gesture_event.x, gesture_event.y),
270        cc::InputHandler::Gesture);
271    UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
272                              scroll_status,
273                              cc::InputHandler::ScrollStatusCount);
274    switch (scroll_status) {
275      case cc::InputHandler::ScrollStarted:
276        TRACE_EVENT_INSTANT0("input",
277                             "InputHandlerProxy::handle_input gesture scroll",
278                             TRACE_EVENT_SCOPE_THREAD);
279        gesture_scroll_on_impl_thread_ = true;
280        return DID_HANDLE;
281      case cc::InputHandler::ScrollUnknown:
282      case cc::InputHandler::ScrollOnMainThread:
283        return DID_NOT_HANDLE;
284      case cc::InputHandler::ScrollIgnored:
285        return DROP_EVENT;
286      case cc::InputHandler::ScrollStatusCount:
287        NOTREACHED();
288        break;
289    }
290  } else if (event.type == WebInputEvent::GestureScrollUpdate) {
291#ifndef NDEBUG
292    DCHECK(expect_scroll_update_end_);
293#endif
294
295    if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_)
296      return DID_NOT_HANDLE;
297
298    const WebGestureEvent& gesture_event =
299        *static_cast<const WebGestureEvent*>(&event);
300    bool did_scroll = input_handler_->ScrollBy(
301        gfx::Point(gesture_event.x, gesture_event.y),
302        gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX,
303                       -gesture_event.data.scrollUpdate.deltaY));
304    return did_scroll ? DID_HANDLE : DROP_EVENT;
305  } else if (event.type == WebInputEvent::GestureScrollEnd) {
306#ifndef NDEBUG
307    DCHECK(expect_scroll_update_end_);
308    expect_scroll_update_end_ = false;
309#endif
310    input_handler_->ScrollEnd();
311
312    if (!gesture_scroll_on_impl_thread_)
313      return DID_NOT_HANDLE;
314
315    gesture_scroll_on_impl_thread_ = false;
316    return DID_HANDLE;
317  } else if (event.type == WebInputEvent::GesturePinchBegin) {
318    input_handler_->PinchGestureBegin();
319    DCHECK(!gesture_pinch_on_impl_thread_);
320    gesture_pinch_on_impl_thread_ = true;
321    return DID_HANDLE;
322  } else if (event.type == WebInputEvent::GesturePinchEnd) {
323    DCHECK(gesture_pinch_on_impl_thread_);
324    gesture_pinch_on_impl_thread_ = false;
325    input_handler_->PinchGestureEnd();
326    return DID_HANDLE;
327  } else if (event.type == WebInputEvent::GesturePinchUpdate) {
328    DCHECK(gesture_pinch_on_impl_thread_);
329    const WebGestureEvent& gesture_event =
330        *static_cast<const WebGestureEvent*>(&event);
331    input_handler_->PinchGestureUpdate(
332        gesture_event.data.pinchUpdate.scale,
333        gfx::Point(gesture_event.x, gesture_event.y));
334    return DID_HANDLE;
335  } else if (event.type == WebInputEvent::GestureFlingStart) {
336    const WebGestureEvent& gesture_event =
337        *static_cast<const WebGestureEvent*>(&event);
338    return HandleGestureFling(gesture_event);
339  } else if (event.type == WebInputEvent::GestureFlingCancel) {
340    if (CancelCurrentFling())
341      return DID_HANDLE;
342    else if (!fling_may_be_active_on_main_thread_)
343      return DROP_EVENT;
344  } else if (event.type == WebInputEvent::TouchStart) {
345    const WebTouchEvent& touch_event =
346        *static_cast<const WebTouchEvent*>(&event);
347    for (size_t i = 0; i < touch_event.touchesLength; ++i) {
348      if (touch_event.touches[i].state != WebTouchPoint::StatePressed)
349        continue;
350      if (input_handler_->HaveTouchEventHandlersAt(
351              gfx::Point(touch_event.touches[i].position.x,
352                         touch_event.touches[i].position.y))) {
353        return DID_NOT_HANDLE;
354      }
355    }
356    return DROP_EVENT;
357  } else if (WebInputEvent::isKeyboardEventType(event.type)) {
358    // Only call |CancelCurrentFling()| if a fling was active, as it will
359    // otherwise disrupt an in-progress touch scroll.
360    if (fling_curve_)
361      CancelCurrentFling();
362  } else if (event.type == WebInputEvent::MouseMove) {
363    const WebMouseEvent& mouse_event =
364        *static_cast<const WebMouseEvent*>(&event);
365    // TODO(tony): Ignore when mouse buttons are down?
366    // TODO(davemoore): This should never happen, but bug #326635 showed some
367    // surprising crashes.
368    CHECK(input_handler_);
369    input_handler_->MouseMoveAt(gfx::Point(mouse_event.x, mouse_event.y));
370  }
371
372  return DID_NOT_HANDLE;
373}
374
375InputHandlerProxy::EventDisposition
376InputHandlerProxy::HandleGestureFling(
377    const WebGestureEvent& gesture_event) {
378  cc::InputHandler::ScrollStatus scroll_status;
379
380  if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) {
381    scroll_status = input_handler_->ScrollBegin(
382        gfx::Point(gesture_event.x, gesture_event.y),
383        cc::InputHandler::NonBubblingGesture);
384  } else {
385    if (!gesture_scroll_on_impl_thread_)
386      scroll_status = cc::InputHandler::ScrollOnMainThread;
387    else
388      scroll_status = input_handler_->FlingScrollBegin();
389  }
390
391#ifndef NDEBUG
392  expect_scroll_update_end_ = false;
393#endif
394
395  switch (scroll_status) {
396    case cc::InputHandler::ScrollStarted: {
397      if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad)
398        input_handler_->ScrollEnd();
399
400      const float vx = gesture_event.data.flingStart.velocityX;
401      const float vy = gesture_event.data.flingStart.velocityY;
402      current_fling_velocity_ = gfx::Vector2dF(vx, vy);
403      fling_curve_.reset(client_->CreateFlingAnimationCurve(
404          gesture_event.sourceDevice,
405          WebFloatPoint(vx, vy),
406          blink::WebSize()));
407      disallow_horizontal_fling_scroll_ = !vx;
408      disallow_vertical_fling_scroll_ = !vy;
409      TRACE_EVENT_ASYNC_BEGIN2("input",
410                               "InputHandlerProxy::HandleGestureFling::started",
411                               this,
412                               "vx",
413                               vx,
414                               "vy",
415                               vy);
416      // Note that the timestamp will only be used to kickstart the animation if
417      // its sufficiently close to the timestamp of the first call |Animate()|.
418      has_fling_animation_started_ = false;
419      fling_parameters_.startTime = gesture_event.timeStampSeconds;
420      fling_parameters_.delta = WebFloatPoint(vx, vy);
421      fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
422      fling_parameters_.globalPoint =
423          WebPoint(gesture_event.globalX, gesture_event.globalY);
424      fling_parameters_.modifiers = gesture_event.modifiers;
425      fling_parameters_.sourceDevice = gesture_event.sourceDevice;
426      input_handler_->SetNeedsAnimate();
427      return DID_HANDLE;
428    }
429    case cc::InputHandler::ScrollUnknown:
430    case cc::InputHandler::ScrollOnMainThread: {
431      TRACE_EVENT_INSTANT0("input",
432                           "InputHandlerProxy::HandleGestureFling::"
433                           "scroll_on_main_thread",
434                           TRACE_EVENT_SCOPE_THREAD);
435      fling_may_be_active_on_main_thread_ = true;
436      return DID_NOT_HANDLE;
437    }
438    case cc::InputHandler::ScrollIgnored: {
439      TRACE_EVENT_INSTANT0(
440          "input",
441          "InputHandlerProxy::HandleGestureFling::ignored",
442          TRACE_EVENT_SCOPE_THREAD);
443      if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) {
444        // We still pass the curve to the main thread if there's nothing
445        // scrollable, in case something
446        // registers a handler before the curve is over.
447        return DID_NOT_HANDLE;
448      }
449      return DROP_EVENT;
450    }
451    case cc::InputHandler::ScrollStatusCount:
452      NOTREACHED();
453      break;
454  }
455  return DID_NOT_HANDLE;
456}
457
458bool InputHandlerProxy::FilterInputEventForFlingBoosting(
459    const WebInputEvent& event) {
460  if (!WebInputEvent::isGestureEventType(event.type))
461    return false;
462
463  if (!fling_curve_) {
464    DCHECK(!deferred_fling_cancel_time_seconds_);
465    return false;
466  }
467
468  const WebGestureEvent& gesture_event =
469      static_cast<const WebGestureEvent&>(event);
470  if (gesture_event.type == WebInputEvent::GestureFlingCancel) {
471    if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
472      return false;
473
474    TRACE_EVENT_INSTANT0("input",
475                         "InputHandlerProxy::FlingBoostStart",
476                         TRACE_EVENT_SCOPE_THREAD);
477    deferred_fling_cancel_time_seconds_ =
478        event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
479    return true;
480  }
481
482  // A fling is either inactive or is "free spinning", i.e., has yet to be
483  // interrupted by a touch gesture, in which case there is nothing to filter.
484  if (!deferred_fling_cancel_time_seconds_)
485    return false;
486
487  // Gestures from a different source should immediately interrupt the fling.
488  if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) {
489    CancelCurrentFling();
490    return false;
491  }
492
493  switch (gesture_event.type) {
494    case WebInputEvent::GestureTapCancel:
495    case WebInputEvent::GestureTapDown:
496      return false;
497
498    case WebInputEvent::GestureScrollBegin:
499      if (!input_handler_->IsCurrentlyScrollingLayerAt(
500              gfx::Point(gesture_event.x, gesture_event.y),
501              fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad
502                  ? cc::InputHandler::NonBubblingGesture
503                  : cc::InputHandler::Gesture)) {
504        CancelCurrentFling();
505        return false;
506      }
507
508      // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
509      // determine if the ScrollBegin should immediately cancel the fling.
510      ExtendBoostedFlingTimeout(gesture_event);
511      return true;
512
513    case WebInputEvent::GestureScrollUpdate: {
514      const double time_since_last_boost_event =
515          event.timeStampSeconds - last_fling_boost_event_.timeStampSeconds;
516      if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_,
517                                               gesture_event,
518                                               time_since_last_boost_event)) {
519        ExtendBoostedFlingTimeout(gesture_event);
520        return true;
521      }
522
523      CancelCurrentFling();
524      return false;
525    }
526
527    case WebInputEvent::GestureScrollEnd:
528      // Clear the last fling boost event *prior* to fling cancellation,
529      // preventing insertion of a synthetic GestureScrollBegin.
530      last_fling_boost_event_ = WebGestureEvent();
531      CancelCurrentFling();
532      return true;
533
534    case WebInputEvent::GestureFlingStart: {
535      DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice);
536
537      bool fling_boosted =
538          fling_parameters_.modifiers == gesture_event.modifiers &&
539          ShouldBoostFling(current_fling_velocity_, gesture_event);
540
541      gfx::Vector2dF new_fling_velocity(
542          gesture_event.data.flingStart.velocityX,
543          gesture_event.data.flingStart.velocityY);
544
545      if (fling_boosted)
546        current_fling_velocity_ += new_fling_velocity;
547      else
548        current_fling_velocity_ = new_fling_velocity;
549
550      WebFloatPoint velocity(current_fling_velocity_.x(),
551                             current_fling_velocity_.y());
552      deferred_fling_cancel_time_seconds_ = 0;
553      disallow_horizontal_fling_scroll_ = !velocity.x;
554      disallow_vertical_fling_scroll_ = !velocity.y;
555      last_fling_boost_event_ = WebGestureEvent();
556      fling_curve_.reset(client_->CreateFlingAnimationCurve(
557          gesture_event.sourceDevice,
558          velocity,
559          blink::WebSize()));
560      fling_parameters_.startTime = gesture_event.timeStampSeconds;
561      fling_parameters_.delta = velocity;
562      fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
563      fling_parameters_.globalPoint =
564          WebPoint(gesture_event.globalX, gesture_event.globalY);
565
566      TRACE_EVENT_INSTANT2("input",
567                           fling_boosted ? "InputHandlerProxy::FlingBoosted"
568                                         : "InputHandlerProxy::FlingReplaced",
569                           TRACE_EVENT_SCOPE_THREAD,
570                           "vx",
571                           current_fling_velocity_.x(),
572                           "vy",
573                           current_fling_velocity_.y());
574
575      // The client expects balanced calls between a consumed GestureFlingStart
576      // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
577      // |DidStopFlinging()| and only send after the accumulated fling ends.
578      client_->DidStopFlinging();
579      return true;
580    }
581
582    default:
583      // All other types of gestures (taps, presses, etc...) will complete the
584      // deferred fling cancellation.
585      CancelCurrentFling();
586      return false;
587  }
588}
589
590void InputHandlerProxy::ExtendBoostedFlingTimeout(
591    const blink::WebGestureEvent& event) {
592  TRACE_EVENT_INSTANT0("input",
593                       "InputHandlerProxy::ExtendBoostedFlingTimeout",
594                       TRACE_EVENT_SCOPE_THREAD);
595  deferred_fling_cancel_time_seconds_ =
596      event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
597  last_fling_boost_event_ = event;
598}
599
600void InputHandlerProxy::Animate(base::TimeTicks time) {
601  if (!fling_curve_)
602    return;
603
604  double monotonic_time_sec = InSecondsF(time);
605
606  if (deferred_fling_cancel_time_seconds_ &&
607      monotonic_time_sec > deferred_fling_cancel_time_seconds_) {
608    CancelCurrentFling();
609    return;
610  }
611
612  if (!has_fling_animation_started_) {
613    has_fling_animation_started_ = true;
614    // Guard against invalid, future or sufficiently stale start times, as there
615    // are no guarantees fling event and animation timestamps are compatible.
616    if (!fling_parameters_.startTime ||
617        monotonic_time_sec <= fling_parameters_.startTime ||
618        monotonic_time_sec >= fling_parameters_.startTime +
619                                  kMaxSecondsFromFlingTimestampToFirstAnimate) {
620      fling_parameters_.startTime = monotonic_time_sec;
621      input_handler_->SetNeedsAnimate();
622      return;
623    }
624  }
625
626  bool fling_is_active =
627      fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
628                          this);
629
630  if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_)
631    fling_is_active = false;
632
633  if (fling_is_active) {
634    input_handler_->SetNeedsAnimate();
635  } else {
636    TRACE_EVENT_INSTANT0("input",
637                         "InputHandlerProxy::animate::flingOver",
638                         TRACE_EVENT_SCOPE_THREAD);
639    CancelCurrentFling();
640  }
641}
642
643void InputHandlerProxy::MainThreadHasStoppedFlinging() {
644  fling_may_be_active_on_main_thread_ = false;
645  client_->DidStopFlinging();
646}
647
648void InputHandlerProxy::DidOverscroll(
649    const gfx::PointF& causal_event_viewport_point,
650    const gfx::Vector2dF& accumulated_overscroll,
651    const gfx::Vector2dF& latest_overscroll_delta) {
652  DCHECK(client_);
653
654  TRACE_EVENT2("input",
655               "InputHandlerProxy::DidOverscroll",
656               "dx",
657               latest_overscroll_delta.x(),
658               "dy",
659               latest_overscroll_delta.y());
660
661  DidOverscrollParams params;
662  params.accumulated_overscroll = accumulated_overscroll;
663  params.latest_overscroll_delta = latest_overscroll_delta;
664  params.current_fling_velocity =
665      ToClientScrollIncrement(current_fling_velocity_);
666  params.causal_event_viewport_point = causal_event_viewport_point;
667
668  if (fling_curve_) {
669    static const int kFlingOverscrollThreshold = 1;
670    disallow_horizontal_fling_scroll_ |=
671        std::abs(params.accumulated_overscroll.x()) >=
672        kFlingOverscrollThreshold;
673    disallow_vertical_fling_scroll_ |=
674        std::abs(params.accumulated_overscroll.y()) >=
675        kFlingOverscrollThreshold;
676  }
677
678  client_->DidOverscroll(params);
679}
680
681bool InputHandlerProxy::CancelCurrentFling() {
682  if (CancelCurrentFlingWithoutNotifyingClient()) {
683    client_->DidStopFlinging();
684    return true;
685  }
686  return false;
687}
688
689bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() {
690  bool had_fling_animation = fling_curve_;
691  if (had_fling_animation &&
692      fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchscreen) {
693    input_handler_->ScrollEnd();
694    TRACE_EVENT_ASYNC_END0(
695        "input",
696        "InputHandlerProxy::HandleGestureFling::started",
697        this);
698  }
699
700  TRACE_EVENT_INSTANT1("input",
701                       "InputHandlerProxy::CancelCurrentFling",
702                       TRACE_EVENT_SCOPE_THREAD,
703                       "had_fling_animation",
704                       had_fling_animation);
705  fling_curve_.reset();
706  has_fling_animation_started_ = false;
707  gesture_scroll_on_impl_thread_ = false;
708  current_fling_velocity_ = gfx::Vector2dF();
709  fling_parameters_ = blink::WebActiveWheelFlingParameters();
710
711  if (deferred_fling_cancel_time_seconds_) {
712    deferred_fling_cancel_time_seconds_ = 0;
713
714    WebGestureEvent last_fling_boost_event = last_fling_boost_event_;
715    last_fling_boost_event_ = WebGestureEvent();
716    if (last_fling_boost_event.type == WebInputEvent::GestureScrollBegin ||
717        last_fling_boost_event.type == WebInputEvent::GestureScrollUpdate) {
718      // Synthesize a GestureScrollBegin, as the original was suppressed.
719      HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event));
720    }
721  }
722
723  return had_fling_animation;
724}
725
726bool InputHandlerProxy::TouchpadFlingScroll(
727    const WebFloatSize& increment) {
728  WebMouseWheelEvent synthetic_wheel;
729  synthetic_wheel.type = WebInputEvent::MouseWheel;
730  synthetic_wheel.deltaX = increment.width;
731  synthetic_wheel.deltaY = increment.height;
732  synthetic_wheel.hasPreciseScrollingDeltas = true;
733  synthetic_wheel.x = fling_parameters_.point.x;
734  synthetic_wheel.y = fling_parameters_.point.y;
735  synthetic_wheel.globalX = fling_parameters_.globalPoint.x;
736  synthetic_wheel.globalY = fling_parameters_.globalPoint.y;
737  synthetic_wheel.modifiers = fling_parameters_.modifiers;
738
739  InputHandlerProxy::EventDisposition disposition =
740      HandleInputEvent(synthetic_wheel);
741  switch (disposition) {
742    case DID_HANDLE:
743      return true;
744    case DROP_EVENT:
745      break;
746    case DID_NOT_HANDLE:
747      TRACE_EVENT_INSTANT0("input",
748                           "InputHandlerProxy::scrollBy::AbortFling",
749                           TRACE_EVENT_SCOPE_THREAD);
750      // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
751      // main thread. In this case we need to schedule a commit and transfer the
752      // fling curve over to the main thread and run the rest of the wheels from
753      // there. This can happen when flinging a page that contains a scrollable
754      // subarea that we can't scroll on the thread if the fling starts outside
755      // the subarea but then is flung "under" the pointer.
756      client_->TransferActiveWheelFlingAnimation(fling_parameters_);
757      fling_may_be_active_on_main_thread_ = true;
758      CancelCurrentFlingWithoutNotifyingClient();
759      break;
760  }
761
762  return false;
763}
764
765bool InputHandlerProxy::scrollBy(const WebFloatSize& increment,
766                                 const WebFloatSize& velocity) {
767  WebFloatSize clipped_increment;
768  WebFloatSize clipped_velocity;
769  if (!disallow_horizontal_fling_scroll_) {
770    clipped_increment.width = increment.width;
771    clipped_velocity.width = velocity.width;
772  }
773  if (!disallow_vertical_fling_scroll_) {
774    clipped_increment.height = increment.height;
775    clipped_velocity.height = velocity.height;
776  }
777
778  current_fling_velocity_ = clipped_velocity;
779
780  // Early out if the increment is zero, but avoid early terimination if the
781  // velocity is still non-zero.
782  if (clipped_increment == WebFloatSize())
783    return clipped_velocity != WebFloatSize();
784
785  TRACE_EVENT2("input",
786               "InputHandlerProxy::scrollBy",
787               "x",
788               clipped_increment.width,
789               "y",
790               clipped_increment.height);
791
792  bool did_scroll = false;
793
794  switch (fling_parameters_.sourceDevice) {
795    case blink::WebGestureDeviceTouchpad:
796      did_scroll = TouchpadFlingScroll(clipped_increment);
797      break;
798    case blink::WebGestureDeviceTouchscreen:
799      clipped_increment = ToClientScrollIncrement(clipped_increment);
800      did_scroll = input_handler_->ScrollBy(fling_parameters_.point,
801                                            clipped_increment);
802      break;
803  }
804
805  if (did_scroll) {
806    fling_parameters_.cumulativeScroll.width += clipped_increment.width;
807    fling_parameters_.cumulativeScroll.height += clipped_increment.height;
808  }
809
810  // It's possible the provided |increment| is sufficiently small as to not
811  // trigger a scroll, e.g., with a trivial time delta between fling updates.
812  // Return true in this case to prevent early fling termination.
813  if (std::abs(clipped_increment.width) < kScrollEpsilon &&
814      std::abs(clipped_increment.height) < kScrollEpsilon)
815    return true;
816
817  return did_scroll;
818}
819
820}  // namespace content
821