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/synthetic_smooth_scroll_gesture.h"
6
7#include "base/logging.h"
8#include "ui/gfx/point_f.h"
9
10namespace content {
11namespace {
12
13gfx::Vector2d FloorTowardZero(const gfx::Vector2dF& vector) {
14  int x = vector.x() > 0 ? floor(vector.x()) : ceil(vector.x());
15  int y = vector.y() > 0 ? floor(vector.y()) : ceil(vector.y());
16  return gfx::Vector2d(x, y);
17}
18
19gfx::Vector2d CeilFromZero(const gfx::Vector2dF& vector) {
20  int x = vector.x() > 0 ? ceil(vector.x()) : floor(vector.x());
21  int y = vector.y() > 0 ? ceil(vector.y()) : floor(vector.y());
22  return gfx::Vector2d(x, y);
23}
24
25gfx::Vector2dF ProjectScalarOntoVector(
26    float scalar, const gfx::Vector2d& vector) {
27  return gfx::ScaleVector2d(vector, scalar / vector.Length());
28}
29
30}  // namespace
31
32SyntheticSmoothScrollGesture::SyntheticSmoothScrollGesture(
33    const SyntheticSmoothScrollGestureParams& params)
34    : params_(params),
35      gesture_source_type_(SyntheticGestureParams::DEFAULT_INPUT),
36      state_(SETUP) {}
37
38SyntheticSmoothScrollGesture::~SyntheticSmoothScrollGesture() {}
39
40SyntheticGesture::Result SyntheticSmoothScrollGesture::ForwardInputEvents(
41    const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
42  if (state_ == SETUP) {
43    gesture_source_type_ = params_.gesture_source_type;
44    if (gesture_source_type_ == SyntheticGestureParams::DEFAULT_INPUT)
45      gesture_source_type_ = target->GetDefaultSyntheticGestureSourceType();
46
47    state_ = STARTED;
48    current_scroll_segment_ = -1;
49    current_scroll_segment_stop_time_ = timestamp;
50  }
51
52  DCHECK_NE(gesture_source_type_, SyntheticGestureParams::DEFAULT_INPUT);
53  if (gesture_source_type_ == SyntheticGestureParams::TOUCH_INPUT)
54    ForwardTouchInputEvents(timestamp, target);
55  else if (gesture_source_type_ == SyntheticGestureParams::MOUSE_INPUT)
56    ForwardMouseInputEvents(timestamp, target);
57  else
58    return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
59
60  return (state_ == DONE) ? SyntheticGesture::GESTURE_FINISHED
61                          : SyntheticGesture::GESTURE_RUNNING;
62}
63
64void SyntheticSmoothScrollGesture::ForwardTouchInputEvents(
65    const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
66  base::TimeTicks event_timestamp = timestamp;
67  switch (state_) {
68    case STARTED:
69      if (ScrollIsNoOp()) {
70        state_ = DONE;
71        break;
72      }
73      AddTouchSlopToFirstDistance(target);
74      ComputeNextScrollSegment();
75      current_scroll_segment_start_position_ = params_.anchor;
76      PressTouchPoint(target, event_timestamp);
77      state_ = MOVING;
78      break;
79    case MOVING: {
80      event_timestamp = ClampTimestamp(timestamp);
81      gfx::Vector2dF delta = GetPositionDeltaAtTime(event_timestamp);
82      MoveTouchPoint(target, delta, event_timestamp);
83
84      if (FinishedCurrentScrollSegment(event_timestamp)) {
85        if (!IsLastScrollSegment()) {
86          current_scroll_segment_start_position_ +=
87              params_.distances[current_scroll_segment_];
88          ComputeNextScrollSegment();
89        } else if (params_.prevent_fling) {
90          state_ = STOPPING;
91        } else {
92          ReleaseTouchPoint(target, event_timestamp);
93          state_ = DONE;
94        }
95      }
96    } break;
97    case STOPPING:
98      if (timestamp - current_scroll_segment_stop_time_ >=
99          target->PointerAssumedStoppedTime()) {
100        event_timestamp = current_scroll_segment_stop_time_ +
101                          target->PointerAssumedStoppedTime();
102        // Send one last move event, but don't change the location. Without this
103        // we'd still sometimes cause a fling on Android.
104
105        // Required to suppress flings on Aura, see
106        // |UpdateWebTouchPointFromUIEvent|, remove when crbug.com/332418
107        // is fixed.
108        touch_event_.touches[0].position.y += 0.001f;
109
110        ForwardTouchEvent(target, event_timestamp);
111        ReleaseTouchPoint(target, event_timestamp);
112        state_ = DONE;
113      }
114      break;
115    case SETUP:
116      NOTREACHED()
117          << "State STARTED invalid for synthetic scroll using touch input.";
118    case DONE:
119      NOTREACHED()
120          << "State DONE invalid for synthetic scroll using touch input.";
121  }
122}
123
124void SyntheticSmoothScrollGesture::ForwardMouseInputEvents(
125    const base::TimeTicks& timestamp, SyntheticGestureTarget* target) {
126  switch (state_) {
127    case STARTED:
128      if (ScrollIsNoOp()) {
129        state_ = DONE;
130        break;
131      }
132      ComputeNextScrollSegment();
133      state_ = MOVING;
134      // Fall through to forward the first event.
135    case MOVING: {
136      // Even though WebMouseWheelEvents take floating point deltas,
137      // internally the scroll position is stored as an integer. We therefore
138      // keep track of the discrete delta which is consistent with the
139      // internal scrolling state. This ensures that when the gesture has
140      // finished we've scrolled exactly the specified distance.
141      base::TimeTicks event_timestamp = ClampTimestamp(timestamp);
142      gfx::Vector2dF current_scroll_segment_total_delta =
143          GetPositionDeltaAtTime(event_timestamp);
144      gfx::Vector2d delta_discrete =
145          FloorTowardZero(current_scroll_segment_total_delta -
146                          current_scroll_segment_total_delta_discrete_);
147      ForwardMouseWheelEvent(target, delta_discrete, event_timestamp);
148      current_scroll_segment_total_delta_discrete_ += delta_discrete;
149
150      if (FinishedCurrentScrollSegment(event_timestamp)) {
151        if (!IsLastScrollSegment()) {
152          current_scroll_segment_total_delta_discrete_ = gfx::Vector2d();
153          ComputeNextScrollSegment();
154          ForwardMouseInputEvents(timestamp, target);
155        } else {
156          state_ = DONE;
157        }
158      }
159    } break;
160    case SETUP:
161      NOTREACHED()
162          << "State STARTED invalid for synthetic scroll using touch input.";
163    case STOPPING:
164      NOTREACHED()
165          << "State STOPPING invalid for synthetic scroll using touch input.";
166    case DONE:
167      NOTREACHED()
168          << "State DONE invalid for synthetic scroll using touch input.";
169  }
170}
171
172void SyntheticSmoothScrollGesture::ForwardTouchEvent(
173    SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
174  touch_event_.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
175
176  target->DispatchInputEventToPlatform(touch_event_);
177}
178
179void SyntheticSmoothScrollGesture::ForwardMouseWheelEvent(
180    SyntheticGestureTarget* target,
181    const gfx::Vector2dF& delta,
182    const base::TimeTicks& timestamp) const {
183  blink::WebMouseWheelEvent mouse_wheel_event =
184      SyntheticWebMouseWheelEventBuilder::Build(delta.x(), delta.y(), 0, false);
185
186  mouse_wheel_event.x = params_.anchor.x();
187  mouse_wheel_event.y = params_.anchor.y();
188
189  mouse_wheel_event.timeStampSeconds = ConvertTimestampToSeconds(timestamp);
190
191  target->DispatchInputEventToPlatform(mouse_wheel_event);
192}
193
194void SyntheticSmoothScrollGesture::PressTouchPoint(
195    SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
196  DCHECK_EQ(current_scroll_segment_, 0);
197  touch_event_.PressPoint(params_.anchor.x(), params_.anchor.y());
198  ForwardTouchEvent(target, timestamp);
199}
200
201void SyntheticSmoothScrollGesture::MoveTouchPoint(
202    SyntheticGestureTarget* target,
203    const gfx::Vector2dF& delta,
204    const base::TimeTicks& timestamp) {
205  DCHECK_GE(current_scroll_segment_, 0);
206  DCHECK_LT(current_scroll_segment_,
207            static_cast<int>(params_.distances.size()));
208  gfx::PointF touch_position = current_scroll_segment_start_position_ + delta;
209  touch_event_.MovePoint(0, touch_position.x(), touch_position.y());
210  ForwardTouchEvent(target, timestamp);
211}
212
213void SyntheticSmoothScrollGesture::ReleaseTouchPoint(
214    SyntheticGestureTarget* target, const base::TimeTicks& timestamp) {
215  DCHECK_EQ(current_scroll_segment_,
216            static_cast<int>(params_.distances.size()) - 1);
217  touch_event_.ReleasePoint(0);
218  ForwardTouchEvent(target, timestamp);
219}
220
221void SyntheticSmoothScrollGesture::AddTouchSlopToFirstDistance(
222    SyntheticGestureTarget* target) {
223  DCHECK_GE(params_.distances.size(), 1ul);
224  gfx::Vector2d& first_scroll_distance = params_.distances[0];
225  DCHECK_GT(first_scroll_distance.Length(), 0);
226  first_scroll_distance += CeilFromZero(ProjectScalarOntoVector(
227      target->GetTouchSlopInDips(), first_scroll_distance));
228}
229
230gfx::Vector2dF SyntheticSmoothScrollGesture::GetPositionDeltaAtTime(
231    const base::TimeTicks& timestamp) const {
232  // Make sure the final delta is correct. Using the computation below can lead
233  // to issues with floating point precision.
234  if (FinishedCurrentScrollSegment(timestamp))
235    return params_.distances[current_scroll_segment_];
236
237  float delta_length =
238      params_.speed_in_pixels_s *
239      (timestamp - current_scroll_segment_start_time_).InSecondsF();
240  return ProjectScalarOntoVector(delta_length,
241                                 params_.distances[current_scroll_segment_]);
242}
243
244void SyntheticSmoothScrollGesture::ComputeNextScrollSegment() {
245  current_scroll_segment_++;
246  DCHECK_LT(current_scroll_segment_,
247            static_cast<int>(params_.distances.size()));
248  int64 total_duration_in_us = static_cast<int64>(
249      1e6 * (params_.distances[current_scroll_segment_].Length() /
250             params_.speed_in_pixels_s));
251  DCHECK_GT(total_duration_in_us, 0);
252  current_scroll_segment_start_time_ = current_scroll_segment_stop_time_;
253  current_scroll_segment_stop_time_ =
254      current_scroll_segment_start_time_ +
255      base::TimeDelta::FromMicroseconds(total_duration_in_us);
256}
257
258base::TimeTicks SyntheticSmoothScrollGesture::ClampTimestamp(
259    const base::TimeTicks& timestamp) const {
260  return std::min(timestamp, current_scroll_segment_stop_time_);
261}
262
263bool SyntheticSmoothScrollGesture::FinishedCurrentScrollSegment(
264    const base::TimeTicks& timestamp) const {
265  return timestamp >= current_scroll_segment_stop_time_;
266}
267
268bool SyntheticSmoothScrollGesture::IsLastScrollSegment() const {
269  DCHECK_LT(current_scroll_segment_,
270            static_cast<int>(params_.distances.size()));
271  return current_scroll_segment_ ==
272         static_cast<int>(params_.distances.size()) - 1;
273}
274
275bool SyntheticSmoothScrollGesture::ScrollIsNoOp() const {
276  return params_.distances.size() == 0 || params_.distances[0].IsZero();
277}
278
279}  // namespace content
280