OLD | NEW |
| (Empty) |
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 <algorithm> | |
8 | |
9 #include "base/auto_reset.h" | |
10 #include "base/command_line.h" | |
11 #include "base/location.h" | |
12 #include "base/logging.h" | |
13 #include "base/metrics/histogram.h" | |
14 #include "base/single_thread_task_runner.h" | |
15 #include "base/thread_task_runner_handle.h" | |
16 #include "base/trace_event/trace_event.h" | |
17 #include "content/common/input/did_overscroll_params.h" | |
18 #include "content/common/input/web_input_event_traits.h" | |
19 #include "content/public/common/content_switches.h" | |
20 #include "content/renderer/input/input_handler_proxy_client.h" | |
21 #include "content/renderer/input/input_scroll_elasticity_controller.h" | |
22 #include "third_party/WebKit/public/platform/Platform.h" | |
23 #include "third_party/WebKit/public/web/WebInputEvent.h" | |
24 #include "ui/events/latency_info.h" | |
25 #include "ui/gfx/geometry/point_conversions.h" | |
26 | |
27 using blink::WebFloatPoint; | |
28 using blink::WebFloatSize; | |
29 using blink::WebGestureEvent; | |
30 using blink::WebInputEvent; | |
31 using blink::WebMouseEvent; | |
32 using blink::WebMouseWheelEvent; | |
33 using blink::WebPoint; | |
34 using blink::WebTouchEvent; | |
35 using blink::WebTouchPoint; | |
36 | |
37 namespace { | |
38 | |
39 // Maximum time between a fling event's timestamp and the first |Animate| call | |
40 // for the fling curve to use the fling timestamp as the initial animation time. | |
41 // Two frames allows a minor delay between event creation and the first animate. | |
42 const double kMaxSecondsFromFlingTimestampToFirstAnimate = 2. / 60.; | |
43 | |
44 // Threshold for determining whether a fling scroll delta should have caused the | |
45 // client to scroll. | |
46 const float kScrollEpsilon = 0.1f; | |
47 | |
48 // Minimum fling velocity required for the active fling and new fling for the | |
49 // two to accumulate. | |
50 const double kMinBoostFlingSpeedSquare = 350. * 350.; | |
51 | |
52 // Minimum velocity for the active touch scroll to preserve (boost) an active | |
53 // fling for which cancellation has been deferred. | |
54 const double kMinBoostTouchScrollSpeedSquare = 150 * 150.; | |
55 | |
56 // Timeout window after which the active fling will be cancelled if no animation | |
57 // ticks, scrolls or flings of sufficient velocity relative to the current fling | |
58 // are received. The default value on Android native views is 40ms, but we use a | |
59 // slightly increased value to accomodate small IPC message delays. | |
60 const double kFlingBoostTimeoutDelaySeconds = 0.05; | |
61 | |
62 gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) { | |
63 return gfx::Vector2dF(-increment.width, -increment.height); | |
64 } | |
65 | |
66 double InSecondsF(const base::TimeTicks& time) { | |
67 return (time - base::TimeTicks()).InSecondsF(); | |
68 } | |
69 | |
70 bool ShouldSuppressScrollForFlingBoosting( | |
71 const gfx::Vector2dF& current_fling_velocity, | |
72 const WebGestureEvent& scroll_update_event, | |
73 double time_since_last_boost_event, | |
74 double time_since_last_fling_animate) { | |
75 DCHECK_EQ(WebInputEvent::GestureScrollUpdate, scroll_update_event.type); | |
76 | |
77 gfx::Vector2dF dx(scroll_update_event.data.scrollUpdate.deltaX, | |
78 scroll_update_event.data.scrollUpdate.deltaY); | |
79 if (gfx::DotProduct(current_fling_velocity, dx) <= 0) | |
80 return false; | |
81 | |
82 if (time_since_last_fling_animate > kFlingBoostTimeoutDelaySeconds) | |
83 return false; | |
84 | |
85 if (time_since_last_boost_event < 0.001) | |
86 return true; | |
87 | |
88 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|. | |
89 // The scroll must be of sufficient velocity to maintain the active fling. | |
90 const gfx::Vector2dF scroll_velocity = | |
91 gfx::ScaleVector2d(dx, 1. / time_since_last_boost_event); | |
92 if (scroll_velocity.LengthSquared() < kMinBoostTouchScrollSpeedSquare) | |
93 return false; | |
94 | |
95 return true; | |
96 } | |
97 | |
98 bool ShouldBoostFling(const gfx::Vector2dF& current_fling_velocity, | |
99 const WebGestureEvent& fling_start_event) { | |
100 DCHECK_EQ(WebInputEvent::GestureFlingStart, fling_start_event.type); | |
101 | |
102 gfx::Vector2dF new_fling_velocity( | |
103 fling_start_event.data.flingStart.velocityX, | |
104 fling_start_event.data.flingStart.velocityY); | |
105 | |
106 if (gfx::DotProduct(current_fling_velocity, new_fling_velocity) <= 0) | |
107 return false; | |
108 | |
109 if (current_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare) | |
110 return false; | |
111 | |
112 if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare) | |
113 return false; | |
114 | |
115 return true; | |
116 } | |
117 | |
118 WebGestureEvent ObtainGestureScrollBegin(const WebGestureEvent& event) { | |
119 WebGestureEvent scroll_begin_event = event; | |
120 scroll_begin_event.type = WebInputEvent::GestureScrollBegin; | |
121 scroll_begin_event.data.scrollBegin.deltaXHint = 0; | |
122 scroll_begin_event.data.scrollBegin.deltaYHint = 0; | |
123 return scroll_begin_event; | |
124 } | |
125 | |
126 void ReportInputEventLatencyUma(const WebInputEvent& event, | |
127 const ui::LatencyInfo& latency_info) { | |
128 if (!(event.type == WebInputEvent::GestureScrollBegin || | |
129 event.type == WebInputEvent::GestureScrollUpdate || | |
130 event.type == WebInputEvent::GesturePinchBegin || | |
131 event.type == WebInputEvent::GesturePinchUpdate || | |
132 event.type == WebInputEvent::GestureFlingStart)) { | |
133 return; | |
134 } | |
135 | |
136 ui::LatencyInfo::LatencyMap::const_iterator it = | |
137 latency_info.latency_components().find(std::make_pair( | |
138 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0)); | |
139 | |
140 if (it == latency_info.latency_components().end()) | |
141 return; | |
142 | |
143 base::TimeDelta delta = base::TimeTicks::Now() - it->second.event_time; | |
144 for (size_t i = 0; i < it->second.event_count; ++i) { | |
145 switch (event.type) { | |
146 case blink::WebInputEvent::GestureScrollBegin: | |
147 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
148 "Event.Latency.RendererImpl.GestureScrollBegin", | |
149 delta.InMicroseconds(), 1, 1000000, 100); | |
150 break; | |
151 case blink::WebInputEvent::GestureScrollUpdate: | |
152 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
153 // So named for historical reasons. | |
154 "Event.Latency.RendererImpl.GestureScroll2", | |
155 delta.InMicroseconds(), 1, 1000000, 100); | |
156 break; | |
157 case blink::WebInputEvent::GesturePinchBegin: | |
158 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
159 "Event.Latency.RendererImpl.GesturePinchBegin", | |
160 delta.InMicroseconds(), 1, 1000000, 100); | |
161 break; | |
162 case blink::WebInputEvent::GesturePinchUpdate: | |
163 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
164 "Event.Latency.RendererImpl.GesturePinchUpdate", | |
165 delta.InMicroseconds(), 1, 1000000, 100); | |
166 break; | |
167 case blink::WebInputEvent::GestureFlingStart: | |
168 UMA_HISTOGRAM_CUSTOM_COUNTS( | |
169 "Event.Latency.RendererImpl.GestureFlingStart", | |
170 delta.InMicroseconds(), 1, 1000000, 100); | |
171 break; | |
172 default: | |
173 NOTREACHED(); | |
174 break; | |
175 } | |
176 } | |
177 } | |
178 | |
179 } // namespace | |
180 | |
181 namespace content { | |
182 | |
183 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler, | |
184 InputHandlerProxyClient* client) | |
185 : client_(client), | |
186 input_handler_(input_handler), | |
187 deferred_fling_cancel_time_seconds_(0), | |
188 synchronous_input_handler_(nullptr), | |
189 allow_root_animate_(true), | |
190 #ifndef NDEBUG | |
191 expect_scroll_update_end_(false), | |
192 #endif | |
193 gesture_scroll_on_impl_thread_(false), | |
194 gesture_pinch_on_impl_thread_(false), | |
195 fling_may_be_active_on_main_thread_(false), | |
196 disallow_horizontal_fling_scroll_(false), | |
197 disallow_vertical_fling_scroll_(false), | |
198 has_fling_animation_started_(false), | |
199 uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()) { | |
200 DCHECK(client); | |
201 input_handler_->BindToClient(this); | |
202 smooth_scroll_enabled_ = base::CommandLine::ForCurrentProcess()->HasSwitch( | |
203 switches::kEnableSmoothScrolling); | |
204 cc::ScrollElasticityHelper* scroll_elasticity_helper = | |
205 input_handler_->CreateScrollElasticityHelper(); | |
206 if (scroll_elasticity_helper) { | |
207 scroll_elasticity_controller_.reset( | |
208 new InputScrollElasticityController(scroll_elasticity_helper)); | |
209 } | |
210 } | |
211 | |
212 InputHandlerProxy::~InputHandlerProxy() {} | |
213 | |
214 void InputHandlerProxy::WillShutdown() { | |
215 scroll_elasticity_controller_.reset(); | |
216 input_handler_ = NULL; | |
217 client_->WillShutdown(); | |
218 } | |
219 | |
220 InputHandlerProxy::EventDisposition | |
221 InputHandlerProxy::HandleInputEventWithLatencyInfo( | |
222 const WebInputEvent& event, | |
223 ui::LatencyInfo* latency_info) { | |
224 DCHECK(input_handler_); | |
225 | |
226 if (uma_latency_reporting_enabled_) | |
227 ReportInputEventLatencyUma(event, *latency_info); | |
228 | |
229 TRACE_EVENT_WITH_FLOW1("input,benchmark", | |
230 "LatencyInfo.Flow", | |
231 TRACE_ID_DONT_MANGLE(latency_info->trace_id()), | |
232 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, | |
233 "step", "HandleInputEventImpl"); | |
234 | |
235 scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor = | |
236 input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info); | |
237 InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event); | |
238 return disposition; | |
239 } | |
240 | |
241 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent( | |
242 const WebInputEvent& event) { | |
243 DCHECK(input_handler_); | |
244 TRACE_EVENT1("input,benchmark", "InputHandlerProxy::HandleInputEvent", | |
245 "type", WebInputEventTraits::GetName(event.type)); | |
246 | |
247 if (FilterInputEventForFlingBoosting(event)) | |
248 return DID_HANDLE; | |
249 | |
250 switch (event.type) { | |
251 case WebInputEvent::MouseWheel: | |
252 return HandleMouseWheel(static_cast<const WebMouseWheelEvent&>(event)); | |
253 | |
254 case WebInputEvent::GestureScrollBegin: | |
255 return HandleGestureScrollBegin( | |
256 static_cast<const WebGestureEvent&>(event)); | |
257 | |
258 case WebInputEvent::GestureScrollUpdate: | |
259 return HandleGestureScrollUpdate( | |
260 static_cast<const WebGestureEvent&>(event)); | |
261 | |
262 case WebInputEvent::GestureScrollEnd: | |
263 return HandleGestureScrollEnd(static_cast<const WebGestureEvent&>(event)); | |
264 | |
265 case WebInputEvent::GesturePinchBegin: { | |
266 DCHECK(!gesture_pinch_on_impl_thread_); | |
267 const WebGestureEvent& gesture_event = | |
268 static_cast<const WebGestureEvent&>(event); | |
269 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad && | |
270 input_handler_->HaveWheelEventHandlersAt( | |
271 gfx::Point(gesture_event.x, gesture_event.y))) { | |
272 return DID_NOT_HANDLE; | |
273 } else { | |
274 input_handler_->PinchGestureBegin(); | |
275 gesture_pinch_on_impl_thread_ = true; | |
276 return DID_HANDLE; | |
277 } | |
278 } | |
279 | |
280 case WebInputEvent::GesturePinchEnd: | |
281 if (gesture_pinch_on_impl_thread_) { | |
282 gesture_pinch_on_impl_thread_ = false; | |
283 input_handler_->PinchGestureEnd(); | |
284 return DID_HANDLE; | |
285 } else { | |
286 return DID_NOT_HANDLE; | |
287 } | |
288 | |
289 case WebInputEvent::GesturePinchUpdate: { | |
290 if (gesture_pinch_on_impl_thread_) { | |
291 const WebGestureEvent& gesture_event = | |
292 static_cast<const WebGestureEvent&>(event); | |
293 if (gesture_event.data.pinchUpdate.zoomDisabled) | |
294 return DROP_EVENT; | |
295 input_handler_->PinchGestureUpdate( | |
296 gesture_event.data.pinchUpdate.scale, | |
297 gfx::Point(gesture_event.x, gesture_event.y)); | |
298 return DID_HANDLE; | |
299 } else { | |
300 return DID_NOT_HANDLE; | |
301 } | |
302 } | |
303 | |
304 case WebInputEvent::GestureFlingStart: | |
305 return HandleGestureFlingStart( | |
306 *static_cast<const WebGestureEvent*>(&event)); | |
307 | |
308 case WebInputEvent::GestureFlingCancel: | |
309 if (CancelCurrentFling()) | |
310 return DID_HANDLE; | |
311 else if (!fling_may_be_active_on_main_thread_) | |
312 return DROP_EVENT; | |
313 return DID_NOT_HANDLE; | |
314 | |
315 case WebInputEvent::TouchStart: | |
316 return HandleTouchStart(static_cast<const WebTouchEvent&>(event)); | |
317 | |
318 case WebInputEvent::MouseMove: { | |
319 const WebMouseEvent& mouse_event = | |
320 static_cast<const WebMouseEvent&>(event); | |
321 // TODO(tony): Ignore when mouse buttons are down? | |
322 // TODO(davemoore): This should never happen, but bug #326635 showed some | |
323 // surprising crashes. | |
324 CHECK(input_handler_); | |
325 input_handler_->MouseMoveAt(gfx::Point(mouse_event.x, mouse_event.y)); | |
326 return DID_NOT_HANDLE; | |
327 } | |
328 | |
329 default: | |
330 if (WebInputEvent::isKeyboardEventType(event.type)) { | |
331 // Only call |CancelCurrentFling()| if a fling was active, as it will | |
332 // otherwise disrupt an in-progress touch scroll. | |
333 if (fling_curve_) | |
334 CancelCurrentFling(); | |
335 } | |
336 break; | |
337 } | |
338 | |
339 return DID_NOT_HANDLE; | |
340 } | |
341 | |
342 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel( | |
343 const WebMouseWheelEvent& wheel_event) { | |
344 InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE; | |
345 cc::InputHandlerScrollResult scroll_result; | |
346 | |
347 // TODO(ccameron): The rail information should be pushed down into | |
348 // InputHandler. | |
349 gfx::Vector2dF scroll_delta( | |
350 wheel_event.railsMode != WebInputEvent::RailsModeVertical | |
351 ? -wheel_event.deltaX | |
352 : 0, | |
353 wheel_event.railsMode != WebInputEvent::RailsModeHorizontal | |
354 ? -wheel_event.deltaY | |
355 : 0); | |
356 | |
357 if (wheel_event.scrollByPage) { | |
358 // TODO(jamesr): We don't properly handle scroll by page in the compositor | |
359 // thread, so punt it to the main thread. http://crbug.com/236639 | |
360 result = DID_NOT_HANDLE; | |
361 } else if (!wheel_event.canScroll) { | |
362 // Wheel events with |canScroll| == false will not trigger scrolling, | |
363 // only event handlers. Forward to the main thread. | |
364 result = DID_NOT_HANDLE; | |
365 } else if (smooth_scroll_enabled_ && !wheel_event.hasPreciseScrollingDeltas) { | |
366 cc::InputHandler::ScrollStatus scroll_status = | |
367 input_handler_->ScrollAnimated(gfx::Point(wheel_event.x, wheel_event.y), | |
368 scroll_delta); | |
369 switch (scroll_status) { | |
370 case cc::InputHandler::SCROLL_STARTED: | |
371 result = DID_HANDLE; | |
372 break; | |
373 case cc::InputHandler::SCROLL_IGNORED: | |
374 result = DROP_EVENT; | |
375 break; | |
376 default: | |
377 result = DID_NOT_HANDLE; | |
378 break; | |
379 } | |
380 } else { | |
381 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin( | |
382 gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::WHEEL); | |
383 switch (scroll_status) { | |
384 case cc::InputHandler::SCROLL_STARTED: { | |
385 TRACE_EVENT_INSTANT2("input", | |
386 "InputHandlerProxy::handle_input wheel scroll", | |
387 TRACE_EVENT_SCOPE_THREAD, "deltaX", | |
388 scroll_delta.x(), "deltaY", scroll_delta.y()); | |
389 gfx::Point scroll_point(wheel_event.x, wheel_event.y); | |
390 scroll_result = input_handler_->ScrollBy(scroll_point, scroll_delta); | |
391 HandleOverscroll(scroll_point, scroll_result); | |
392 input_handler_->ScrollEnd(); | |
393 result = scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT; | |
394 break; | |
395 } | |
396 case cc::InputHandler::SCROLL_IGNORED: | |
397 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail | |
398 // to properly sync scrollability it's safer to send the event to the | |
399 // main thread. Change back to DROP_EVENT once we have synchronization | |
400 // bugs sorted out. | |
401 result = DID_NOT_HANDLE; | |
402 break; | |
403 case cc::InputHandler::SCROLL_UNKNOWN: | |
404 case cc::InputHandler::SCROLL_ON_MAIN_THREAD: | |
405 result = DID_NOT_HANDLE; | |
406 break; | |
407 case cc::InputHandler::ScrollStatusCount: | |
408 NOTREACHED(); | |
409 break; | |
410 } | |
411 } | |
412 | |
413 // Send the event and its disposition to the elasticity controller to update | |
414 // the over-scroll animation. If the event is to be handled on the main | |
415 // thread, the event and its disposition will be sent to the elasticity | |
416 // controller after being handled on the main thread. | |
417 if (scroll_elasticity_controller_ && result != DID_NOT_HANDLE) { | |
418 // Note that the call to the elasticity controller is made asynchronously, | |
419 // to minimize divergence between main thread and impl thread event | |
420 // handling paths. | |
421 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
422 FROM_HERE, | |
423 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult, | |
424 scroll_elasticity_controller_->GetWeakPtr(), wheel_event, | |
425 scroll_result)); | |
426 } | |
427 return result; | |
428 } | |
429 | |
430 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin( | |
431 const WebGestureEvent& gesture_event) { | |
432 if (gesture_scroll_on_impl_thread_) | |
433 CancelCurrentFling(); | |
434 | |
435 #ifndef NDEBUG | |
436 DCHECK(!expect_scroll_update_end_); | |
437 expect_scroll_update_end_ = true; | |
438 #endif | |
439 cc::InputHandler::ScrollStatus scroll_status; | |
440 if (gesture_event.data.scrollBegin.targetViewport) { | |
441 scroll_status = input_handler_->RootScrollBegin(cc::InputHandler::GESTURE); | |
442 } else { | |
443 scroll_status = input_handler_->ScrollBegin( | |
444 gfx::Point(gesture_event.x, gesture_event.y), | |
445 cc::InputHandler::GESTURE); | |
446 } | |
447 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult", | |
448 scroll_status, | |
449 cc::InputHandler::ScrollStatusCount); | |
450 switch (scroll_status) { | |
451 case cc::InputHandler::SCROLL_STARTED: | |
452 TRACE_EVENT_INSTANT0("input", | |
453 "InputHandlerProxy::handle_input gesture scroll", | |
454 TRACE_EVENT_SCOPE_THREAD); | |
455 gesture_scroll_on_impl_thread_ = true; | |
456 return DID_HANDLE; | |
457 case cc::InputHandler::SCROLL_UNKNOWN: | |
458 case cc::InputHandler::SCROLL_ON_MAIN_THREAD: | |
459 return DID_NOT_HANDLE; | |
460 case cc::InputHandler::SCROLL_IGNORED: | |
461 return DROP_EVENT; | |
462 case cc::InputHandler::ScrollStatusCount: | |
463 NOTREACHED(); | |
464 break; | |
465 } | |
466 return DID_NOT_HANDLE; | |
467 } | |
468 | |
469 InputHandlerProxy::EventDisposition | |
470 InputHandlerProxy::HandleGestureScrollUpdate( | |
471 const WebGestureEvent& gesture_event) { | |
472 #ifndef NDEBUG | |
473 DCHECK(expect_scroll_update_end_); | |
474 #endif | |
475 | |
476 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_) | |
477 return DID_NOT_HANDLE; | |
478 | |
479 gfx::Point scroll_point(gesture_event.x, gesture_event.y); | |
480 gfx::Vector2dF scroll_delta(-gesture_event.data.scrollUpdate.deltaX, | |
481 -gesture_event.data.scrollUpdate.deltaY); | |
482 cc::InputHandlerScrollResult scroll_result = input_handler_->ScrollBy( | |
483 scroll_point, scroll_delta); | |
484 HandleOverscroll(scroll_point, scroll_result); | |
485 return scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT; | |
486 } | |
487 | |
488 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollEnd( | |
489 const WebGestureEvent& gesture_event) { | |
490 #ifndef NDEBUG | |
491 DCHECK(expect_scroll_update_end_); | |
492 expect_scroll_update_end_ = false; | |
493 #endif | |
494 input_handler_->ScrollEnd(); | |
495 if (!gesture_scroll_on_impl_thread_) | |
496 return DID_NOT_HANDLE; | |
497 gesture_scroll_on_impl_thread_ = false; | |
498 return DID_HANDLE; | |
499 } | |
500 | |
501 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureFlingStart( | |
502 const WebGestureEvent& gesture_event) { | |
503 cc::InputHandler::ScrollStatus scroll_status = | |
504 cc::InputHandler::SCROLL_ON_MAIN_THREAD; | |
505 switch (gesture_event.sourceDevice) { | |
506 case blink::WebGestureDeviceTouchpad: | |
507 if (gesture_event.data.flingStart.targetViewport) { | |
508 scroll_status = input_handler_->RootScrollBegin( | |
509 cc::InputHandler::NON_BUBBLING_GESTURE); | |
510 } else { | |
511 scroll_status = input_handler_->ScrollBegin( | |
512 gfx::Point(gesture_event.x, gesture_event.y), | |
513 cc::InputHandler::NON_BUBBLING_GESTURE); | |
514 } | |
515 break; | |
516 case blink::WebGestureDeviceTouchscreen: | |
517 if (!gesture_scroll_on_impl_thread_) | |
518 scroll_status = cc::InputHandler::SCROLL_ON_MAIN_THREAD; | |
519 else | |
520 scroll_status = input_handler_->FlingScrollBegin(); | |
521 break; | |
522 case blink::WebGestureDeviceUninitialized: | |
523 NOTREACHED(); | |
524 return DID_NOT_HANDLE; | |
525 } | |
526 | |
527 #ifndef NDEBUG | |
528 expect_scroll_update_end_ = false; | |
529 #endif | |
530 | |
531 switch (scroll_status) { | |
532 case cc::InputHandler::SCROLL_STARTED: { | |
533 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) | |
534 input_handler_->ScrollEnd(); | |
535 | |
536 const float vx = gesture_event.data.flingStart.velocityX; | |
537 const float vy = gesture_event.data.flingStart.velocityY; | |
538 current_fling_velocity_ = gfx::Vector2dF(vx, vy); | |
539 DCHECK(!current_fling_velocity_.IsZero()); | |
540 fling_curve_.reset(client_->CreateFlingAnimationCurve( | |
541 gesture_event.sourceDevice, | |
542 WebFloatPoint(vx, vy), | |
543 blink::WebSize())); | |
544 disallow_horizontal_fling_scroll_ = !vx; | |
545 disallow_vertical_fling_scroll_ = !vy; | |
546 TRACE_EVENT_ASYNC_BEGIN2("input,benchmark", | |
547 "InputHandlerProxy::HandleGestureFling::started", | |
548 this, "vx", vx, "vy", vy); | |
549 // Note that the timestamp will only be used to kickstart the animation if | |
550 // its sufficiently close to the timestamp of the first call |Animate()|. | |
551 has_fling_animation_started_ = false; | |
552 fling_parameters_.startTime = gesture_event.timeStampSeconds; | |
553 fling_parameters_.delta = WebFloatPoint(vx, vy); | |
554 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y); | |
555 fling_parameters_.globalPoint = | |
556 WebPoint(gesture_event.globalX, gesture_event.globalY); | |
557 fling_parameters_.modifiers = gesture_event.modifiers; | |
558 fling_parameters_.sourceDevice = gesture_event.sourceDevice; | |
559 RequestAnimation(); | |
560 return DID_HANDLE; | |
561 } | |
562 case cc::InputHandler::SCROLL_UNKNOWN: | |
563 case cc::InputHandler::SCROLL_ON_MAIN_THREAD: { | |
564 TRACE_EVENT_INSTANT0("input", | |
565 "InputHandlerProxy::HandleGestureFling::" | |
566 "scroll_on_main_thread", | |
567 TRACE_EVENT_SCOPE_THREAD); | |
568 gesture_scroll_on_impl_thread_ = false; | |
569 fling_may_be_active_on_main_thread_ = true; | |
570 return DID_NOT_HANDLE; | |
571 } | |
572 case cc::InputHandler::SCROLL_IGNORED: { | |
573 TRACE_EVENT_INSTANT0( | |
574 "input", | |
575 "InputHandlerProxy::HandleGestureFling::ignored", | |
576 TRACE_EVENT_SCOPE_THREAD); | |
577 gesture_scroll_on_impl_thread_ = false; | |
578 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) { | |
579 // We still pass the curve to the main thread if there's nothing | |
580 // scrollable, in case something | |
581 // registers a handler before the curve is over. | |
582 return DID_NOT_HANDLE; | |
583 } | |
584 return DROP_EVENT; | |
585 } | |
586 case cc::InputHandler::ScrollStatusCount: | |
587 NOTREACHED(); | |
588 break; | |
589 } | |
590 return DID_NOT_HANDLE; | |
591 } | |
592 | |
593 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart( | |
594 const blink::WebTouchEvent& touch_event) { | |
595 for (size_t i = 0; i < touch_event.touchesLength; ++i) { | |
596 if (touch_event.touches[i].state != WebTouchPoint::StatePressed) | |
597 continue; | |
598 if (input_handler_->DoTouchEventsBlockScrollAt( | |
599 gfx::Point(touch_event.touches[i].position.x, | |
600 touch_event.touches[i].position.y))) { | |
601 // TODO(rbyers): We should consider still sending the touch events to | |
602 // main asynchronously (crbug.com/455539). | |
603 return DID_NOT_HANDLE; | |
604 } | |
605 } | |
606 return DROP_EVENT; | |
607 } | |
608 | |
609 bool InputHandlerProxy::FilterInputEventForFlingBoosting( | |
610 const WebInputEvent& event) { | |
611 if (!WebInputEvent::isGestureEventType(event.type)) | |
612 return false; | |
613 | |
614 if (!fling_curve_) { | |
615 DCHECK(!deferred_fling_cancel_time_seconds_); | |
616 return false; | |
617 } | |
618 | |
619 const WebGestureEvent& gesture_event = | |
620 static_cast<const WebGestureEvent&>(event); | |
621 if (gesture_event.type == WebInputEvent::GestureFlingCancel) { | |
622 if (gesture_event.data.flingCancel.preventBoosting) | |
623 return false; | |
624 | |
625 if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare) | |
626 return false; | |
627 | |
628 TRACE_EVENT_INSTANT0("input", | |
629 "InputHandlerProxy::FlingBoostStart", | |
630 TRACE_EVENT_SCOPE_THREAD); | |
631 deferred_fling_cancel_time_seconds_ = | |
632 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds; | |
633 return true; | |
634 } | |
635 | |
636 // A fling is either inactive or is "free spinning", i.e., has yet to be | |
637 // interrupted by a touch gesture, in which case there is nothing to filter. | |
638 if (!deferred_fling_cancel_time_seconds_) | |
639 return false; | |
640 | |
641 // Gestures from a different source should immediately interrupt the fling. | |
642 if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) { | |
643 CancelCurrentFling(); | |
644 return false; | |
645 } | |
646 | |
647 switch (gesture_event.type) { | |
648 case WebInputEvent::GestureTapCancel: | |
649 case WebInputEvent::GestureTapDown: | |
650 return false; | |
651 | |
652 case WebInputEvent::GestureScrollBegin: | |
653 if (!input_handler_->IsCurrentlyScrollingLayerAt( | |
654 gfx::Point(gesture_event.x, gesture_event.y), | |
655 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad | |
656 ? cc::InputHandler::NON_BUBBLING_GESTURE | |
657 : cc::InputHandler::GESTURE)) { | |
658 CancelCurrentFling(); | |
659 return false; | |
660 } | |
661 | |
662 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to | |
663 // determine if the ScrollBegin should immediately cancel the fling. | |
664 ExtendBoostedFlingTimeout(gesture_event); | |
665 return true; | |
666 | |
667 case WebInputEvent::GestureScrollUpdate: { | |
668 const double time_since_last_boost_event = | |
669 event.timeStampSeconds - last_fling_boost_event_.timeStampSeconds; | |
670 const double time_since_last_fling_animate = std::max( | |
671 0.0, event.timeStampSeconds - InSecondsF(last_fling_animate_time_)); | |
672 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_, | |
673 gesture_event, | |
674 time_since_last_boost_event, | |
675 time_since_last_fling_animate)) { | |
676 ExtendBoostedFlingTimeout(gesture_event); | |
677 return true; | |
678 } | |
679 | |
680 CancelCurrentFling(); | |
681 return false; | |
682 } | |
683 | |
684 case WebInputEvent::GestureScrollEnd: | |
685 // Clear the last fling boost event *prior* to fling cancellation, | |
686 // preventing insertion of a synthetic GestureScrollBegin. | |
687 last_fling_boost_event_ = WebGestureEvent(); | |
688 CancelCurrentFling(); | |
689 return true; | |
690 | |
691 case WebInputEvent::GestureFlingStart: { | |
692 DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice); | |
693 | |
694 bool fling_boosted = | |
695 fling_parameters_.modifiers == gesture_event.modifiers && | |
696 ShouldBoostFling(current_fling_velocity_, gesture_event); | |
697 | |
698 gfx::Vector2dF new_fling_velocity( | |
699 gesture_event.data.flingStart.velocityX, | |
700 gesture_event.data.flingStart.velocityY); | |
701 DCHECK(!new_fling_velocity.IsZero()); | |
702 | |
703 if (fling_boosted) | |
704 current_fling_velocity_ += new_fling_velocity; | |
705 else | |
706 current_fling_velocity_ = new_fling_velocity; | |
707 | |
708 WebFloatPoint velocity(current_fling_velocity_.x(), | |
709 current_fling_velocity_.y()); | |
710 deferred_fling_cancel_time_seconds_ = 0; | |
711 disallow_horizontal_fling_scroll_ = !velocity.x; | |
712 disallow_vertical_fling_scroll_ = !velocity.y; | |
713 last_fling_boost_event_ = WebGestureEvent(); | |
714 fling_curve_.reset(client_->CreateFlingAnimationCurve( | |
715 gesture_event.sourceDevice, | |
716 velocity, | |
717 blink::WebSize())); | |
718 fling_parameters_.startTime = gesture_event.timeStampSeconds; | |
719 fling_parameters_.delta = velocity; | |
720 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y); | |
721 fling_parameters_.globalPoint = | |
722 WebPoint(gesture_event.globalX, gesture_event.globalY); | |
723 | |
724 TRACE_EVENT_INSTANT2("input", | |
725 fling_boosted ? "InputHandlerProxy::FlingBoosted" | |
726 : "InputHandlerProxy::FlingReplaced", | |
727 TRACE_EVENT_SCOPE_THREAD, | |
728 "vx", | |
729 current_fling_velocity_.x(), | |
730 "vy", | |
731 current_fling_velocity_.y()); | |
732 | |
733 // The client expects balanced calls between a consumed GestureFlingStart | |
734 // and |DidStopFlinging()|. | |
735 client_->DidStopFlinging(); | |
736 return true; | |
737 } | |
738 | |
739 default: | |
740 // All other types of gestures (taps, presses, etc...) will complete the | |
741 // deferred fling cancellation. | |
742 CancelCurrentFling(); | |
743 return false; | |
744 } | |
745 } | |
746 | |
747 void InputHandlerProxy::ExtendBoostedFlingTimeout( | |
748 const blink::WebGestureEvent& event) { | |
749 TRACE_EVENT_INSTANT0("input", | |
750 "InputHandlerProxy::ExtendBoostedFlingTimeout", | |
751 TRACE_EVENT_SCOPE_THREAD); | |
752 deferred_fling_cancel_time_seconds_ = | |
753 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds; | |
754 last_fling_boost_event_ = event; | |
755 } | |
756 | |
757 void InputHandlerProxy::Animate(base::TimeTicks time) { | |
758 // If using synchronous animate, then only expect Animate attempts started by | |
759 // the synchronous system. Don't let the InputHandler try to Animate also. | |
760 DCHECK(!input_handler_->IsCurrentlyScrollingInnerViewport() || | |
761 allow_root_animate_); | |
762 | |
763 if (scroll_elasticity_controller_) | |
764 scroll_elasticity_controller_->Animate(time); | |
765 | |
766 if (!fling_curve_) | |
767 return; | |
768 | |
769 last_fling_animate_time_ = time; | |
770 double monotonic_time_sec = InSecondsF(time); | |
771 | |
772 if (deferred_fling_cancel_time_seconds_ && | |
773 monotonic_time_sec > deferred_fling_cancel_time_seconds_) { | |
774 CancelCurrentFling(); | |
775 return; | |
776 } | |
777 | |
778 client_->DidAnimateForInput(); | |
779 | |
780 if (!has_fling_animation_started_) { | |
781 has_fling_animation_started_ = true; | |
782 // Guard against invalid, future or sufficiently stale start times, as there | |
783 // are no guarantees fling event and animation timestamps are compatible. | |
784 if (!fling_parameters_.startTime || | |
785 monotonic_time_sec <= fling_parameters_.startTime || | |
786 monotonic_time_sec >= fling_parameters_.startTime + | |
787 kMaxSecondsFromFlingTimestampToFirstAnimate) { | |
788 fling_parameters_.startTime = monotonic_time_sec; | |
789 RequestAnimation(); | |
790 return; | |
791 } | |
792 } | |
793 | |
794 bool fling_is_active = | |
795 fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime, | |
796 this); | |
797 | |
798 if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_) | |
799 fling_is_active = false; | |
800 | |
801 if (fling_is_active) { | |
802 RequestAnimation(); | |
803 } else { | |
804 TRACE_EVENT_INSTANT0("input", | |
805 "InputHandlerProxy::animate::flingOver", | |
806 TRACE_EVENT_SCOPE_THREAD); | |
807 CancelCurrentFling(); | |
808 } | |
809 } | |
810 | |
811 void InputHandlerProxy::MainThreadHasStoppedFlinging() { | |
812 fling_may_be_active_on_main_thread_ = false; | |
813 client_->DidStopFlinging(); | |
814 } | |
815 | |
816 void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() { | |
817 if (scroll_elasticity_controller_) | |
818 scroll_elasticity_controller_->ReconcileStretchAndScroll(); | |
819 } | |
820 | |
821 void InputHandlerProxy::UpdateRootLayerStateForSynchronousInputHandler( | |
822 const gfx::ScrollOffset& total_scroll_offset, | |
823 const gfx::ScrollOffset& max_scroll_offset, | |
824 const gfx::SizeF& scrollable_size, | |
825 float page_scale_factor, | |
826 float min_page_scale_factor, | |
827 float max_page_scale_factor) { | |
828 if (synchronous_input_handler_) { | |
829 synchronous_input_handler_->UpdateRootLayerState( | |
830 total_scroll_offset, max_scroll_offset, scrollable_size, | |
831 page_scale_factor, min_page_scale_factor, max_page_scale_factor); | |
832 } | |
833 } | |
834 | |
835 void InputHandlerProxy::SetOnlySynchronouslyAnimateRootFlings( | |
836 SynchronousInputHandler* synchronous_input_handler) { | |
837 allow_root_animate_ = !synchronous_input_handler; | |
838 synchronous_input_handler_ = synchronous_input_handler; | |
839 if (synchronous_input_handler_) | |
840 input_handler_->RequestUpdateForSynchronousInputHandler(); | |
841 } | |
842 | |
843 void InputHandlerProxy::SynchronouslyAnimate(base::TimeTicks time) { | |
844 // When this function is used, SetOnlySynchronouslyAnimate() should have been | |
845 // previously called. IOW you should either be entirely in synchronous mode or | |
846 // not. | |
847 DCHECK(synchronous_input_handler_); | |
848 DCHECK(!allow_root_animate_); | |
849 base::AutoReset<bool> reset(&allow_root_animate_, true); | |
850 Animate(time); | |
851 } | |
852 | |
853 void InputHandlerProxy::SynchronouslySetRootScrollOffset( | |
854 const gfx::ScrollOffset& root_offset) { | |
855 DCHECK(synchronous_input_handler_); | |
856 input_handler_->SetSynchronousInputHandlerRootScrollOffset(root_offset); | |
857 } | |
858 | |
859 void InputHandlerProxy::HandleOverscroll( | |
860 const gfx::Point& causal_event_viewport_point, | |
861 const cc::InputHandlerScrollResult& scroll_result) { | |
862 DCHECK(client_); | |
863 if (!scroll_result.did_overscroll_root) | |
864 return; | |
865 | |
866 TRACE_EVENT2("input", | |
867 "InputHandlerProxy::DidOverscroll", | |
868 "dx", | |
869 scroll_result.unused_scroll_delta.x(), | |
870 "dy", | |
871 scroll_result.unused_scroll_delta.y()); | |
872 | |
873 DidOverscrollParams params; | |
874 params.accumulated_overscroll = scroll_result.accumulated_root_overscroll; | |
875 params.latest_overscroll_delta = scroll_result.unused_scroll_delta; | |
876 params.current_fling_velocity = | |
877 ToClientScrollIncrement(current_fling_velocity_); | |
878 params.causal_event_viewport_point = gfx::PointF(causal_event_viewport_point); | |
879 | |
880 if (fling_curve_) { | |
881 static const int kFlingOverscrollThreshold = 1; | |
882 disallow_horizontal_fling_scroll_ |= | |
883 std::abs(params.accumulated_overscroll.x()) >= | |
884 kFlingOverscrollThreshold; | |
885 disallow_vertical_fling_scroll_ |= | |
886 std::abs(params.accumulated_overscroll.y()) >= | |
887 kFlingOverscrollThreshold; | |
888 } | |
889 | |
890 client_->DidOverscroll(params); | |
891 } | |
892 | |
893 bool InputHandlerProxy::CancelCurrentFling() { | |
894 if (CancelCurrentFlingWithoutNotifyingClient()) { | |
895 client_->DidStopFlinging(); | |
896 return true; | |
897 } | |
898 return false; | |
899 } | |
900 | |
901 bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() { | |
902 bool had_fling_animation = fling_curve_; | |
903 if (had_fling_animation && | |
904 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchscreen) { | |
905 input_handler_->ScrollEnd(); | |
906 TRACE_EVENT_ASYNC_END0( | |
907 "input", | |
908 "InputHandlerProxy::HandleGestureFling::started", | |
909 this); | |
910 } | |
911 | |
912 TRACE_EVENT_INSTANT1("input", | |
913 "InputHandlerProxy::CancelCurrentFling", | |
914 TRACE_EVENT_SCOPE_THREAD, | |
915 "had_fling_animation", | |
916 had_fling_animation); | |
917 fling_curve_.reset(); | |
918 has_fling_animation_started_ = false; | |
919 gesture_scroll_on_impl_thread_ = false; | |
920 current_fling_velocity_ = gfx::Vector2dF(); | |
921 fling_parameters_ = blink::WebActiveWheelFlingParameters(); | |
922 | |
923 if (deferred_fling_cancel_time_seconds_) { | |
924 deferred_fling_cancel_time_seconds_ = 0; | |
925 | |
926 WebGestureEvent last_fling_boost_event = last_fling_boost_event_; | |
927 last_fling_boost_event_ = WebGestureEvent(); | |
928 if (last_fling_boost_event.type == WebInputEvent::GestureScrollBegin || | |
929 last_fling_boost_event.type == WebInputEvent::GestureScrollUpdate) { | |
930 // Synthesize a GestureScrollBegin, as the original was suppressed. | |
931 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event)); | |
932 } | |
933 } | |
934 | |
935 return had_fling_animation; | |
936 } | |
937 | |
938 void InputHandlerProxy::RequestAnimation() { | |
939 // When a SynchronousInputHandler is present, root flings should go through | |
940 // it to allow it to control when or if the root fling is animated. Non-root | |
941 // flings always go through the normal InputHandler. | |
942 if (synchronous_input_handler_ && | |
943 input_handler_->IsCurrentlyScrollingInnerViewport()) | |
944 synchronous_input_handler_->SetNeedsSynchronousAnimateInput(); | |
945 else | |
946 input_handler_->SetNeedsAnimateInput(); | |
947 } | |
948 | |
949 bool InputHandlerProxy::TouchpadFlingScroll( | |
950 const WebFloatSize& increment) { | |
951 WebMouseWheelEvent synthetic_wheel; | |
952 synthetic_wheel.type = WebInputEvent::MouseWheel; | |
953 synthetic_wheel.deltaX = increment.width; | |
954 synthetic_wheel.deltaY = increment.height; | |
955 synthetic_wheel.hasPreciseScrollingDeltas = true; | |
956 synthetic_wheel.x = fling_parameters_.point.x; | |
957 synthetic_wheel.y = fling_parameters_.point.y; | |
958 synthetic_wheel.globalX = fling_parameters_.globalPoint.x; | |
959 synthetic_wheel.globalY = fling_parameters_.globalPoint.y; | |
960 synthetic_wheel.modifiers = fling_parameters_.modifiers; | |
961 | |
962 InputHandlerProxy::EventDisposition disposition = | |
963 HandleInputEvent(synthetic_wheel); | |
964 switch (disposition) { | |
965 case DID_HANDLE: | |
966 return true; | |
967 case DROP_EVENT: | |
968 break; | |
969 case DID_NOT_HANDLE: | |
970 TRACE_EVENT_INSTANT0("input", | |
971 "InputHandlerProxy::scrollBy::AbortFling", | |
972 TRACE_EVENT_SCOPE_THREAD); | |
973 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the | |
974 // main thread. In this case we need to schedule a commit and transfer the | |
975 // fling curve over to the main thread and run the rest of the wheels from | |
976 // there. This can happen when flinging a page that contains a scrollable | |
977 // subarea that we can't scroll on the thread if the fling starts outside | |
978 // the subarea but then is flung "under" the pointer. | |
979 client_->TransferActiveWheelFlingAnimation(fling_parameters_); | |
980 fling_may_be_active_on_main_thread_ = true; | |
981 CancelCurrentFlingWithoutNotifyingClient(); | |
982 break; | |
983 } | |
984 | |
985 return false; | |
986 } | |
987 | |
988 bool InputHandlerProxy::scrollBy(const WebFloatSize& increment, | |
989 const WebFloatSize& velocity) { | |
990 WebFloatSize clipped_increment; | |
991 WebFloatSize clipped_velocity; | |
992 if (!disallow_horizontal_fling_scroll_) { | |
993 clipped_increment.width = increment.width; | |
994 clipped_velocity.width = velocity.width; | |
995 } | |
996 if (!disallow_vertical_fling_scroll_) { | |
997 clipped_increment.height = increment.height; | |
998 clipped_velocity.height = velocity.height; | |
999 } | |
1000 | |
1001 current_fling_velocity_ = clipped_velocity; | |
1002 | |
1003 // Early out if the increment is zero, but avoid early terimination if the | |
1004 // velocity is still non-zero. | |
1005 if (clipped_increment == WebFloatSize()) | |
1006 return clipped_velocity != WebFloatSize(); | |
1007 | |
1008 TRACE_EVENT2("input", | |
1009 "InputHandlerProxy::scrollBy", | |
1010 "x", | |
1011 clipped_increment.width, | |
1012 "y", | |
1013 clipped_increment.height); | |
1014 | |
1015 bool did_scroll = false; | |
1016 | |
1017 switch (fling_parameters_.sourceDevice) { | |
1018 case blink::WebGestureDeviceTouchpad: | |
1019 did_scroll = TouchpadFlingScroll(clipped_increment); | |
1020 break; | |
1021 case blink::WebGestureDeviceTouchscreen: { | |
1022 clipped_increment = ToClientScrollIncrement(clipped_increment); | |
1023 cc::InputHandlerScrollResult scroll_result = input_handler_->ScrollBy( | |
1024 fling_parameters_.point, clipped_increment); | |
1025 HandleOverscroll(fling_parameters_.point, scroll_result); | |
1026 did_scroll = scroll_result.did_scroll; | |
1027 } break; | |
1028 case blink::WebGestureDeviceUninitialized: | |
1029 NOTREACHED(); | |
1030 return false; | |
1031 } | |
1032 | |
1033 if (did_scroll) { | |
1034 fling_parameters_.cumulativeScroll.width += clipped_increment.width; | |
1035 fling_parameters_.cumulativeScroll.height += clipped_increment.height; | |
1036 } | |
1037 | |
1038 // It's possible the provided |increment| is sufficiently small as to not | |
1039 // trigger a scroll, e.g., with a trivial time delta between fling updates. | |
1040 // Return true in this case to prevent early fling termination. | |
1041 if (std::abs(clipped_increment.width) < kScrollEpsilon && | |
1042 std::abs(clipped_increment.height) < kScrollEpsilon) | |
1043 return true; | |
1044 | |
1045 return did_scroll; | |
1046 } | |
1047 | |
1048 } // namespace content | |
OLD | NEW |