blob: 73ec5bd08c2a1be9af2020a49eee779200be2bd2 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2012 The Chromium Authors
[email protected]b4df9df2012-11-16 01:58:582// 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/overscroll_controller.h"
6
Mohsen Izadia0091532017-07-06 04:21:057#include <algorithm>
8
Hans Wennborg0917de892020-04-28 20:21:159#include "base/check_op.h"
[email protected]3aad43b2013-11-05 20:20:1810#include "base/command_line.h"
Hans Wennborg0917de892020-04-28 20:21:1511#include "base/notreached.h"
[email protected]b4df9df2012-11-16 01:58:5812#include "content/browser/renderer_host/overscroll_controller_delegate.h"
[email protected]1cd76c2d82012-11-29 07:36:4713#include "content/public/browser/overscroll_configuration.h"
Sahel Sharifya29ee2112017-12-15 23:22:5414#include "content/public/common/content_features.h"
[email protected]3aad43b2013-11-05 20:20:1815#include "content/public/common/content_switches.h"
[email protected]b4df9df2012-11-16 01:58:5816
Mohsen Izadia0091532017-07-06 04:21:0517namespace content {
18
[email protected]3aad43b2013-11-05 20:20:1819namespace {
20
Daniel Cheng224569ee2018-04-25 05:45:0621// Minimum amount of time after an actual scroll after which a pull-to-refresh
22// can start.
Peter Kastinge5a38ed2021-10-02 03:06:3523constexpr base::TimeDelta kPullToRefreshCoolOffDelay = base::Milliseconds(600);
Mohsen Izadi5a6583512018-04-23 18:31:1924
dtapuska19ebfa512016-02-19 22:27:4025bool IsGestureEventFromTouchpad(const blink::WebInputEvent& event) {
Blink Reformat1c4d759e2017-04-09 16:34:5426 DCHECK(blink::WebInputEvent::IsGestureEventType(event.GetType()));
dtapuska19ebfa512016-02-19 22:27:4027 const blink::WebGestureEvent& gesture =
28 static_cast<const blink::WebGestureEvent&>(event);
Daniel Cheng7f9ec902019-04-18 05:07:0029 return gesture.SourceDevice() == blink::WebGestureDevice::kTouchpad;
dtapuska19ebfa512016-02-19 22:27:4030}
31
Sahel Sharifybdaa29e2018-09-17 17:42:2532bool IsGestureEventFromAutoscroll(const blink::WebGestureEvent event) {
Daniel Cheng7f9ec902019-04-18 05:07:0033 return event.SourceDevice() == blink::WebGestureDevice::kSyntheticAutoscroll;
Sahel Sharifybdaa29e2018-09-17 17:42:2534}
35
chaopeng9736c522018-05-08 18:32:2736bool IsGestureScrollUpdateInertialEvent(const blink::WebInputEvent& event) {
Dave Tapuska347d60a2020-04-21 23:55:4737 if (event.GetType() != blink::WebInputEvent::Type::kGestureScrollUpdate)
chaopeng9736c522018-05-08 18:32:2738 return false;
39
40 const blink::WebGestureEvent& gesture =
41 static_cast<const blink::WebGestureEvent&>(event);
42 return gesture.data.scroll_update.inertial_phase ==
Daniel Libby6ce6b95a2019-05-10 17:06:2643 blink::WebGestureEvent::InertialPhaseState::kMomentum;
chaopeng9736c522018-05-08 18:32:2744}
45
Mohsen Izadia0091532017-07-06 04:21:0546float ClampAbsoluteValue(float value, float max_abs) {
47 DCHECK_LT(0.f, max_abs);
Baitinqbc4cea12023-04-22 00:48:3948 return std::clamp(value, -max_abs, max_abs);
Mohsen Izadia0091532017-07-06 04:21:0549}
[email protected]3aad43b2013-11-05 20:20:1850
Mohsen Izadia0091532017-07-06 04:21:0551} // namespace
[email protected]b4df9df2012-11-16 01:58:5852
Sahel Sharifye6d81f472018-07-11 20:40:2653OverscrollController::OverscrollController() {}
[email protected]b4df9df2012-11-16 01:58:5854
Mohsen Izadi38795d5d2017-06-30 17:10:4855OverscrollController::~OverscrollController() {}
[email protected]b4df9df2012-11-16 01:58:5856
dtapuska19ebfa512016-02-19 22:27:4057bool OverscrollController::ShouldProcessEvent(
58 const blink::WebInputEvent& event) {
Blink Reformat1c4d759e2017-04-09 16:34:5459 switch (event.GetType()) {
Dave Tapuska347d60a2020-04-21 23:55:4760 case blink::WebInputEvent::Type::kGestureScrollBegin:
61 case blink::WebInputEvent::Type::kGestureScrollUpdate:
62 case blink::WebInputEvent::Type::kGestureScrollEnd: {
dtapuska8c4dae12017-01-13 00:23:0663 const blink::WebGestureEvent& gesture =
64 static_cast<const blink::WebGestureEvent&>(event);
mcnee19fd2492017-06-01 14:42:4365
Sahel Sharifybdaa29e2018-09-17 17:42:2566 // Gesture events with Autoscroll source don't cause overscrolling.
67 if (IsGestureEventFromAutoscroll(gesture))
68 return false;
69
Mohsen Izadiffcbc61f12020-02-09 06:31:2770 ui::ScrollGranularity granularity;
Blink Reformat1c4d759e2017-04-09 16:34:5471 switch (event.GetType()) {
Dave Tapuska347d60a2020-04-21 23:55:4772 case blink::WebInputEvent::Type::kGestureScrollBegin:
Daniel Libbya37d447a2019-05-02 20:06:4873 granularity = gesture.data.scroll_begin.delta_hint_units;
dtapuska8c4dae12017-01-13 00:23:0674 break;
Dave Tapuska347d60a2020-04-21 23:55:4775 case blink::WebInputEvent::Type::kGestureScrollUpdate:
Daniel Libbya37d447a2019-05-02 20:06:4876 granularity = gesture.data.scroll_update.delta_units;
dtapuska8c4dae12017-01-13 00:23:0677 break;
Dave Tapuska347d60a2020-04-21 23:55:4778 case blink::WebInputEvent::Type::kGestureScrollEnd:
Daniel Libbya37d447a2019-05-02 20:06:4879 granularity = gesture.data.scroll_end.delta_units;
dtapuska8c4dae12017-01-13 00:23:0680 break;
81 default:
Mohsen Izadiffcbc61f12020-02-09 06:31:2782 granularity = ui::ScrollGranularity::kScrollByPixel;
dtapuska8c4dae12017-01-13 00:23:0683 break;
dtapuska19ebfa512016-02-19 22:27:4084 }
dtapuska8c4dae12017-01-13 00:23:0685
Mohsen Izadiffcbc61f12020-02-09 06:31:2786 return granularity == ui::ScrollGranularity::kScrollByPrecisePixel;
dtapuska19ebfa512016-02-19 22:27:4087 }
dtapuska8c4dae12017-01-13 00:23:0688 default:
89 break;
90 }
dtapuska19ebfa512016-02-19 22:27:4091 return true;
92}
93
chaopenga7585d72018-04-10 18:02:3894bool OverscrollController::ShouldIgnoreInertialEvent(
95 const blink::WebInputEvent& event) const {
chaopeng9736c522018-05-08 18:32:2796 return ignore_following_inertial_events_ &&
97 IsGestureScrollUpdateInertialEvent(event);
chaopenga7585d72018-04-10 18:02:3898 }
99
[email protected]277857a2014-06-03 10:38:01100bool OverscrollController::WillHandleEvent(const blink::WebInputEvent& event) {
dtapuska19ebfa512016-02-19 22:27:40101 if (!ShouldProcessEvent(event))
102 return false;
103
Mohsen Izadi9830436c2017-12-21 18:02:46104 // TODO(mohsen): Consider filtering mouse-wheel events during overscroll. See
105 // https://crbug.com/772106.
Dave Tapuska347d60a2020-04-21 23:55:47106 if (event.GetType() == blink::WebInputEvent::Type::kMouseWheel)
Mohsen Izadi9830436c2017-12-21 18:02:46107 return false;
108
Dave Tapuska347d60a2020-04-21 23:55:47109 if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollBegin) {
Mohsen Izadi5a6583512018-04-23 18:31:19110 ignore_following_inertial_events_ = false;
chaopeng9736c522018-05-08 18:32:27111 first_inertial_event_time_.reset();
Mohsen Izadi5a6583512018-04-23 18:31:19112 time_since_last_ignored_scroll_ =
Daniel Cheng224569ee2018-04-25 05:45:06113 event.TimeStamp() - last_ignored_scroll_time_;
Mohsen Izadi5a6583512018-04-23 18:31:19114 // Will handle events when processing ACKs to ensure the correct order.
115 return false;
116 }
117
Dave Tapuska347d60a2020-04-21 23:55:47118 if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollEnd) {
Mohsen Izadi5a6583512018-04-23 18:31:19119 if (scroll_state_ == ScrollState::CONTENT_CONSUMING ||
120 overscroll_ignored_) {
Daniel Cheng224569ee2018-04-25 05:45:06121 last_ignored_scroll_time_ = event.TimeStamp();
Mohsen Izadi5a6583512018-04-23 18:31:19122 }
Chong Zhang064c2052017-08-29 16:05:31123 // Will handle events when processing ACKs to ensure the correct order.
124 return false;
125 }
126
chaopeng1d469b02018-04-05 15:41:07127 // Consume the scroll-update events if they are from a inertial scroll (fling)
128 // event that completed an overscroll gesture.
chaopenga7585d72018-04-10 18:02:38129 if (ShouldIgnoreInertialEvent(event))
130 return true;
chaopeng1d469b02018-04-05 15:41:07131
mfomitchev8a785bef2015-04-13 18:35:05132 bool reset_scroll_state = false;
Mohsen Izadi9830436c2017-12-21 18:02:46133 if (scroll_state_ != ScrollState::NONE || overscroll_delta_x_ ||
134 overscroll_delta_y_) {
Blink Reformat1c4d759e2017-04-09 16:34:54135 switch (event.GetType()) {
Dave Tapuska347d60a2020-04-21 23:55:47136 case blink::WebInputEvent::Type::kGestureFlingStart:
mfomitchev8a785bef2015-04-13 18:35:05137 reset_scroll_state = true;
[email protected]b31beeb2013-06-17 15:54:27138 break;
139
[email protected]b31beeb2013-06-17 15:54:27140 default:
Blink Reformat1c4d759e2017-04-09 16:34:54141 if (blink::WebInputEvent::IsMouseEventType(event.GetType()) ||
142 blink::WebInputEvent::IsKeyboardEventType(event.GetType())) {
mfomitchev8a785bef2015-04-13 18:35:05143 reset_scroll_state = true;
[email protected]b31beeb2013-06-17 15:54:27144 }
145 break;
[email protected]a4141642013-05-23 04:10:58146 }
147 }
148
Mohsen Izadi50180f392017-09-08 19:29:12149 if (reset_scroll_state)
150 ResetScrollState();
mfomitchev8a785bef2015-04-13 18:35:05151
[email protected]b4df9df2012-11-16 01:58:58152 if (DispatchEventCompletesAction(event)) {
153 CompleteAction();
[email protected]9f4f47e2012-11-20 02:21:43154
[email protected]277857a2014-06-03 10:38:01155 // Let the event be dispatched to the renderer.
156 return false;
[email protected]b4df9df2012-11-16 01:58:58157 }
158
159 if (overscroll_mode_ != OVERSCROLL_NONE && DispatchEventResetsState(event)) {
mfomitchev09f0d64a2017-03-02 19:40:07160 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
[email protected]b4df9df2012-11-16 01:58:58161
162 // Let the event be dispatched to the renderer.
[email protected]277857a2014-06-03 10:38:01163 return false;
[email protected]b4df9df2012-11-16 01:58:58164 }
165
166 if (overscroll_mode_ != OVERSCROLL_NONE) {
[email protected]2422e5c2013-09-13 19:05:02167 // Consume the event only if it updates the overscroll state.
168 if (ProcessEventForOverscroll(event))
[email protected]277857a2014-06-03 10:38:01169 return true;
mfomitchev8a785bef2015-04-13 18:35:05170 } else if (reset_scroll_state) {
171 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
[email protected]b4df9df2012-11-16 01:58:58172 }
173
Mohsen Izadi50180f392017-09-08 19:29:12174 // In overscrolling state, consume scroll-update and fling-start events when
175 // they do not contribute to overscroll in order to prevent content scroll.
David Bokanc7fee172020-10-05 16:39:13176 // TODO(bokan): This needs to account for behavior_ somehow since if the page
177 // declares that it doesn't want an overscroll effect, we should allow
178 // sending the scroll update events to generate DOM overscroll events.
179 // https://crbug.com/1112183.
Mohsen Izadi9830436c2017-12-21 18:02:46180 return scroll_state_ == ScrollState::OVERSCROLLING &&
Dave Tapuska347d60a2020-04-21 23:55:47181 (event.GetType() == blink::WebInputEvent::Type::kGestureScrollUpdate ||
182 event.GetType() == blink::WebInputEvent::Type::kGestureFlingStart);
[email protected]b4df9df2012-11-16 01:58:58183}
184
Sandra Suncbd6da42018-01-05 15:29:36185void OverscrollController::OnDidOverscroll(
186 const ui::DidOverscrollParams& params) {
187 // TODO(sunyunjia): We should also decide whether to trigger overscroll,
188 // update scroll_state_ here. See https://crbug.com/799467.
Mohsen Izadi80189412018-03-22 17:10:18189 behavior_ = params.overscroll_behavior;
Sandra Suncbd6da42018-01-05 15:29:36190}
191
[email protected]180ef242013-11-07 06:50:46192void OverscrollController::ReceivedEventACK(const blink::WebInputEvent& event,
[email protected]b4df9df2012-11-16 01:58:58193 bool processed) {
dtapuska19ebfa512016-02-19 22:27:40194 if (!ShouldProcessEvent(event))
195 return;
196
chaopenga7585d72018-04-10 18:02:38197 // An inertial scroll (fling) event from a completed overscroll gesture
198 // should not modify states below.
199 if (ShouldIgnoreInertialEvent(event))
200 return;
201
[email protected]a4141642013-05-23 04:10:58202 if (processed) {
203 // If a scroll event is consumed by the page, i.e. some content on the page
204 // has been scrolled, then there is not going to be an overscroll gesture,
205 // until the current scroll ends, and a new scroll gesture starts.
Mohsen Izadi9830436c2017-12-21 18:02:46206 // Similarly, if a mouse-wheel event is consumed, probably the page has
207 // implemented its own scroll-like behavior and no overscroll should happen.
208 if (scroll_state_ == ScrollState::NONE &&
Dave Tapuska347d60a2020-04-21 23:55:47209 (event.GetType() == blink::WebInputEvent::Type::kGestureScrollUpdate ||
210 event.GetType() == blink::WebInputEvent::Type::kMouseWheel)) {
Mohsen Izadi9830436c2017-12-21 18:02:46211 scroll_state_ = ScrollState::CONTENT_CONSUMING;
[email protected]a4141642013-05-23 04:10:58212 }
Mohsen Izadi50180f392017-09-08 19:29:12213 // In overscrolling state, only return if we are in an overscroll mode;
214 // otherwise, we would want to ProcessEventForOverscroll to let it start a
215 // new overscroll mode.
Mohsen Izadi9830436c2017-12-21 18:02:46216 if (scroll_state_ != ScrollState::OVERSCROLLING ||
Mohsen Izadi50180f392017-09-08 19:29:12217 overscroll_mode_ != OVERSCROLL_NONE) {
218 return;
219 }
[email protected]a4141642013-05-23 04:10:58220 }
Mohsen Izadi9830436c2017-12-21 18:02:46221
Dave Tapuska347d60a2020-04-21 23:55:47222 if (event.GetType() == blink::WebInputEvent::Type::kMouseWheel)
Mohsen Izadi9830436c2017-12-21 18:02:46223 return;
224
[email protected]b4df9df2012-11-16 01:58:58225 ProcessEventForOverscroll(event);
226}
227
228void OverscrollController::Reset() {
229 overscroll_mode_ = OVERSCROLL_NONE;
Mohsen Izadi38795d5d2017-06-30 17:10:48230 overscroll_source_ = OverscrollSource::NONE;
[email protected]b4df9df2012-11-16 01:58:58231 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
Mohsen Izadi50180f392017-09-08 19:29:12232 ResetScrollState();
[email protected]b4df9df2012-11-16 01:58:58233}
234
[email protected]63ad73832013-06-17 15:41:04235void OverscrollController::Cancel() {
mfomitchev09f0d64a2017-03-02 19:40:07236 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
[email protected]63ad73832013-06-17 15:41:04237 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
Mohsen Izadi50180f392017-09-08 19:29:12238 ResetScrollState();
[email protected]63ad73832013-06-17 15:41:04239}
240
chaopeng1d469b02018-04-05 15:41:07241bool OverscrollController::DispatchEventCompletesAction(
[email protected]180ef242013-11-07 06:50:46242 const blink::WebInputEvent& event) const {
[email protected]b4df9df2012-11-16 01:58:58243 if (overscroll_mode_ == OVERSCROLL_NONE)
244 return false;
Mohsen Izadi4ef8db72017-10-31 00:08:42245 DCHECK_NE(OverscrollSource::NONE, overscroll_source_);
[email protected]b4df9df2012-11-16 01:58:58246
247 // Complete the overscroll gesture if there was a mouse move or a scroll-end
248 // after the threshold.
Dave Tapuska347d60a2020-04-21 23:55:47249 if (event.GetType() != blink::WebInputEvent::Type::kMouseMove &&
250 event.GetType() != blink::WebInputEvent::Type::kGestureScrollEnd &&
251 event.GetType() != blink::WebInputEvent::Type::kGestureFlingStart &&
252 event.GetType() != blink::WebInputEvent::Type::kGestureScrollUpdate)
[email protected]b4df9df2012-11-16 01:58:58253 return false;
254
chaopeng1d469b02018-04-05 15:41:07255 // Complete the overscroll gesture for inertial scroll (fling) event from
256 // touchpad.
Dave Tapuska347d60a2020-04-21 23:55:47257 if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollUpdate) {
chaopeng1d469b02018-04-05 15:41:07258 if (overscroll_source_ != OverscrollSource::TOUCHPAD)
259 return false;
260 DCHECK(IsGestureEventFromTouchpad(event));
261 const blink::WebGestureEvent gesture_event =
262 static_cast<const blink::WebGestureEvent&>(event);
263 if (gesture_event.data.scroll_update.inertial_phase !=
Daniel Libby6ce6b95a2019-05-10 17:06:26264 blink::WebGestureEvent::InertialPhaseState::kMomentum)
chaopeng1d469b02018-04-05 15:41:07265 return false;
266 }
267
Dave Tapuska347d60a2020-04-21 23:55:47268 if (event.GetType() == blink::WebInputEvent::Type::kGestureScrollEnd &&
Mohsen Izadi4ef8db72017-10-31 00:08:42269 overscroll_source_ == OverscrollSource::TOUCHPAD) {
270 DCHECK(IsGestureEventFromTouchpad(event));
Sahel Sharifya29ee2112017-12-15 23:22:54271 // Complete the action for a GSE with touchpad source only when it is in
272 // momentumPhase.
273 const blink::WebGestureEvent gesture_event =
274 static_cast<const blink::WebGestureEvent&>(event);
275 if (gesture_event.data.scroll_end.inertial_phase !=
Daniel Libby6ce6b95a2019-05-10 17:06:26276 blink::WebGestureEvent::InertialPhaseState::kMomentum)
Sahel Sharifya29ee2112017-12-15 23:22:54277 return false;
Mohsen Izadi38795d5d2017-06-30 17:10:48278 }
dtapuska19ebfa512016-02-19 22:27:40279
[email protected]0da15c2d2013-10-30 16:47:20280 if (!delegate_)
281 return false;
282
Dave Tapuska347d60a2020-04-21 23:55:47283 if (event.GetType() == blink::WebInputEvent::Type::kGestureFlingStart) {
[email protected]6658c6a2013-01-23 23:42:22284 // Check to see if the fling is in the same direction of the overscroll.
[email protected]180ef242013-11-07 06:50:46285 const blink::WebGestureEvent gesture =
286 static_cast<const blink::WebGestureEvent&>(event);
[email protected]6658c6a2013-01-23 23:42:22287 switch (overscroll_mode_) {
288 case OVERSCROLL_EAST:
Blink Reformat1c4d759e2017-04-09 16:34:54289 if (gesture.data.fling_start.velocity_x < 0)
[email protected]6658c6a2013-01-23 23:42:22290 return false;
291 break;
292 case OVERSCROLL_WEST:
Blink Reformat1c4d759e2017-04-09 16:34:54293 if (gesture.data.fling_start.velocity_x > 0)
[email protected]6658c6a2013-01-23 23:42:22294 return false;
295 break;
296 case OVERSCROLL_NORTH:
Blink Reformat1c4d759e2017-04-09 16:34:54297 if (gesture.data.fling_start.velocity_y > 0)
[email protected]6658c6a2013-01-23 23:42:22298 return false;
299 break;
300 case OVERSCROLL_SOUTH:
Blink Reformat1c4d759e2017-04-09 16:34:54301 if (gesture.data.fling_start.velocity_y < 0)
[email protected]6658c6a2013-01-23 23:42:22302 return false;
303 break;
304 case OVERSCROLL_NONE:
Peter Boströmfc7ddc182024-10-31 19:37:21305 NOTREACHED();
[email protected]6658c6a2013-01-23 23:42:22306 }
307 }
308
Mohsen Izadi4bd624cc2017-07-21 19:23:51309 const gfx::Size size = delegate_->GetDisplaySize();
Mohsen Izadi38795d5d2017-06-30 17:10:48310 if (size.IsEmpty())
311 return false;
312
Mohsen Izadi4ef8db72017-10-31 00:08:42313 const float delta =
314 overscroll_mode_ == OVERSCROLL_WEST || overscroll_mode_ == OVERSCROLL_EAST
315 ? overscroll_delta_x_
316 : overscroll_delta_y_;
317 const float ratio = fabs(delta) / std::max(size.width(), size.height());
Elly Fong-Jones47960972019-05-16 18:40:59318 const float threshold =
Mohsen Izadi4ef8db72017-10-31 00:08:42319 overscroll_source_ == OverscrollSource::TOUCHPAD
Elly Fong-Jones47960972019-05-16 18:40:59320 ? OverscrollConfig::kCompleteTouchpadThresholdPercent
321 : OverscrollConfig::kCompleteTouchscreenThresholdPercent;
[email protected]b4df9df2012-11-16 01:58:58322 return ratio >= threshold;
323}
324
325bool OverscrollController::DispatchEventResetsState(
[email protected]180ef242013-11-07 06:50:46326 const blink::WebInputEvent& event) const {
Blink Reformat1c4d759e2017-04-09 16:34:54327 switch (event.GetType()) {
Chong Zhang064c2052017-08-29 16:05:31328 // GestureScrollBegin/End ACK will reset overscroll state when necessary.
Dave Tapuska347d60a2020-04-21 23:55:47329 case blink::WebInputEvent::Type::kGestureScrollBegin:
330 case blink::WebInputEvent::Type::kGestureScrollEnd:
331 case blink::WebInputEvent::Type::kGestureScrollUpdate:
332 case blink::WebInputEvent::Type::kGestureFlingCancel:
[email protected]b4df9df2012-11-16 01:58:58333 return false;
334
335 default:
[email protected]c4ddb5c42012-11-17 01:09:57336 // Touch events can arrive during an overscroll gesture initiated by
337 // touch-scrolling. These events should not reset the overscroll state.
Blink Reformat1c4d759e2017-04-09 16:34:54338 return !blink::WebInputEvent::IsTouchEventType(event.GetType());
[email protected]b4df9df2012-11-16 01:58:58339 }
340}
341
[email protected]2422e5c2013-09-13 19:05:02342bool OverscrollController::ProcessEventForOverscroll(
[email protected]180ef242013-11-07 06:50:46343 const blink::WebInputEvent& event) {
[email protected]2422e5c2013-09-13 19:05:02344 bool event_processed = false;
Blink Reformat1c4d759e2017-04-09 16:34:54345 switch (event.GetType()) {
Dave Tapuska347d60a2020-04-21 23:55:47346 case blink::WebInputEvent::Type::kGestureScrollBegin: {
Sahel Sharifye6d81f472018-07-11 20:40:26347 if (overscroll_mode_ != OVERSCROLL_NONE)
Chong Zhang064c2052017-08-29 16:05:31348 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
Chong Zhang064c2052017-08-29 16:05:31349 break;
350 }
Dave Tapuska347d60a2020-04-21 23:55:47351 case blink::WebInputEvent::Type::kGestureScrollEnd: {
Sahel Sharifya29ee2112017-12-15 23:22:54352 // Only reset the state on GestureScrollEnd generated from the touchpad
353 // when the scrolling is in inertial state.
354 const blink::WebGestureEvent gesture_event =
355 static_cast<const blink::WebGestureEvent&>(event);
Daniel Libby6ce6b95a2019-05-10 17:06:26356 bool reset_scroll_state =
357 !IsGestureEventFromTouchpad(event) ||
358 (gesture_event.data.scroll_end.inertial_phase ==
359 blink::WebGestureEvent::InertialPhaseState::kMomentum);
Chong Zhang064c2052017-08-29 16:05:31360
361 if (reset_scroll_state)
Mohsen Izadi50180f392017-09-08 19:29:12362 ResetScrollState();
Chong Zhang064c2052017-08-29 16:05:31363
364 if (DispatchEventCompletesAction(event)) {
365 CompleteAction();
[email protected]2422e5c2013-09-13 19:05:02366 break;
Chong Zhang064c2052017-08-29 16:05:31367 }
368
369 if (!reset_scroll_state)
370 break;
371
372 if (overscroll_mode_ != OVERSCROLL_NONE) {
373 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
374 } else {
375 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
376 }
[email protected]b4df9df2012-11-16 01:58:58377 break;
378 }
Dave Tapuska347d60a2020-04-21 23:55:47379 case blink::WebInputEvent::Type::kGestureScrollUpdate: {
[email protected]180ef242013-11-07 06:50:46380 const blink::WebGestureEvent& gesture =
381 static_cast<const blink::WebGestureEvent&>(event);
chaopeng9736c522018-05-08 18:32:27382 bool is_gesture_scroll_update_inertial_event =
383 IsGestureScrollUpdateInertialEvent(event);
dtapuska19ebfa512016-02-19 22:27:40384 event_processed = ProcessOverscroll(
Blink Reformat1c4d759e2017-04-09 16:34:54385 gesture.data.scroll_update.delta_x,
386 gesture.data.scroll_update.delta_y,
Daniel Cheng7f9ec902019-04-18 05:07:00387 gesture.SourceDevice() == blink::WebGestureDevice::kTouchpad,
chaopeng9736c522018-05-08 18:32:27388 is_gesture_scroll_update_inertial_event);
389 if (is_gesture_scroll_update_inertial_event) {
390 // Record the timestamp of first inertial event.
391 if (!first_inertial_event_time_) {
392 first_inertial_event_time_ = event.TimeStamp();
393 break;
394 }
395 base::TimeDelta inertial_event_interval =
396 event.TimeStamp() - first_inertial_event_time_.value();
397 if (inertial_event_interval >=
398 OverscrollConfig::MaxInertialEventsBeforeOverscrollCancellation()) {
399 ignore_following_inertial_events_ = true;
400 // Reset overscroll state if fling didn't complete the overscroll
401 // gesture within the first 20 inertial events.
402 Cancel();
403 }
404 }
[email protected]b4df9df2012-11-16 01:58:58405 break;
406 }
Dave Tapuska347d60a2020-04-21 23:55:47407 case blink::WebInputEvent::Type::kGestureFlingStart: {
[email protected]b4df9df2012-11-16 01:58:58408 const float kFlingVelocityThreshold = 1100.f;
[email protected]180ef242013-11-07 06:50:46409 const blink::WebGestureEvent& gesture =
410 static_cast<const blink::WebGestureEvent&>(event);
Blink Reformat1c4d759e2017-04-09 16:34:54411 float velocity_x = gesture.data.fling_start.velocity_x;
412 float velocity_y = gesture.data.fling_start.velocity_y;
[email protected]b4df9df2012-11-16 01:58:58413 if (fabs(velocity_x) > kFlingVelocityThreshold) {
414 if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) ||
415 (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) {
416 CompleteAction();
[email protected]2422e5c2013-09-13 19:05:02417 event_processed = true;
[email protected]b4df9df2012-11-16 01:58:58418 break;
419 }
420 } else if (fabs(velocity_y) > kFlingVelocityThreshold) {
421 if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) ||
422 (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) {
423 CompleteAction();
[email protected]2422e5c2013-09-13 19:05:02424 event_processed = true;
[email protected]b4df9df2012-11-16 01:58:58425 break;
426 }
427 }
428
429 // Reset overscroll state if fling didn't complete the overscroll gesture.
mfomitchev09f0d64a2017-03-02 19:40:07430 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
[email protected]b4df9df2012-11-16 01:58:58431 break;
432 }
433
434 default:
Blink Reformat1c4d759e2017-04-09 16:34:54435 DCHECK(blink::WebInputEvent::IsGestureEventType(event.GetType()) ||
436 blink::WebInputEvent::IsTouchEventType(event.GetType()))
437 << "Received unexpected event: " << event.GetType();
[email protected]b4df9df2012-11-16 01:58:58438 }
[email protected]2422e5c2013-09-13 19:05:02439 return event_processed;
[email protected]b4df9df2012-11-16 01:58:58440}
441
[email protected]bfab3d02014-08-20 03:16:55442bool OverscrollController::ProcessOverscroll(float delta_x,
[email protected]a7f99f02013-08-29 14:15:19443 float delta_y,
chaopeng9736c522018-05-08 18:32:27444 bool is_touchpad,
445 bool is_inertial) {
Mohsen Izadi9830436c2017-12-21 18:02:46446 if (scroll_state_ == ScrollState::CONTENT_CONSUMING)
447 return false;
448
chaopeng9736c522018-05-08 18:32:27449 // Do not start overscroll for inertial events.
450 if (overscroll_mode_ == OVERSCROLL_NONE && is_inertial)
451 return false;
452
Mohsen Izadi9830436c2017-12-21 18:02:46453 overscroll_delta_x_ += delta_x;
454 overscroll_delta_y_ += delta_y;
[email protected]b4df9df2012-11-16 01:58:58455
Elly Fong-Jones47960972019-05-16 18:40:59456 const float start_threshold =
457 is_touchpad ? OverscrollConfig::kStartTouchpadThresholdDips
458 : OverscrollConfig::kStartTouchscreenThresholdDips;
Mohsen Izadi4ef8db72017-10-31 00:08:42459 if (fabs(overscroll_delta_x_) <= start_threshold &&
460 fabs(overscroll_delta_y_) <= start_threshold) {
mfomitchev09f0d64a2017-03-02 19:40:07461 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
[email protected]bfab3d02014-08-20 03:16:55462 return true;
[email protected]b4df9df2012-11-16 01:58:58463 }
464
Mohsen Izadia0091532017-07-06 04:21:05465 if (delegate_) {
Arthur Sonzognic686e8f2024-01-11 08:36:37466 std::optional<float> cap = delegate_->GetMaxOverscrollDelta();
Mohsen Izadia0091532017-07-06 04:21:05467 if (cap) {
Mohsen Izadia652de4c2017-07-22 02:30:32468 DCHECK_LE(0.f, cap.value());
Mohsen Izadia0091532017-07-06 04:21:05469 switch (overscroll_mode_) {
470 case OVERSCROLL_WEST:
471 case OVERSCROLL_EAST:
472 overscroll_delta_x_ = ClampAbsoluteValue(
Mohsen Izadi4ef8db72017-10-31 00:08:42473 overscroll_delta_x_, cap.value() + start_threshold);
Mohsen Izadia0091532017-07-06 04:21:05474 break;
475 case OVERSCROLL_NORTH:
476 case OVERSCROLL_SOUTH:
477 overscroll_delta_y_ = ClampAbsoluteValue(
Mohsen Izadi4ef8db72017-10-31 00:08:42478 overscroll_delta_y_, cap.value() + start_threshold);
Mohsen Izadia0091532017-07-06 04:21:05479 break;
480 case OVERSCROLL_NONE:
481 break;
482 }
483 }
484 }
485
[email protected]a23d04af2013-02-19 17:41:10486 // Compute the current overscroll direction. If the direction is different
487 // from the current direction, then always switch to no-overscroll mode first
488 // to make sure that subsequent scroll events go through to the page first.
489 OverscrollMode new_mode = OVERSCROLL_NONE;
[email protected]14749ddb2013-02-23 06:56:10490 const float kMinRatio = 2.5;
Mohsen Izadi4ef8db72017-10-31 00:08:42491 if (fabs(overscroll_delta_x_) > start_threshold &&
[email protected]fa6db252013-07-26 03:38:20492 fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio)
[email protected]a23d04af2013-02-19 17:41:10493 new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST;
Mohsen Izadi4ef8db72017-10-31 00:08:42494 else if (fabs(overscroll_delta_y_) > start_threshold &&
[email protected]fa6db252013-07-26 03:38:20495 fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio)
[email protected]a23d04af2013-02-19 17:41:10496 new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH;
497
chaopengcd44c7622018-04-13 04:08:21498 // The horizontal overscroll is used for history navigation. Enable it for
499 // touchpad only if TouchpadOverscrollHistoryNavigation is enabled.
500 if ((new_mode == OVERSCROLL_EAST || new_mode == OVERSCROLL_WEST) &&
501 is_touchpad &&
502 !OverscrollConfig::TouchpadOverscrollHistoryNavigationEnabled()) {
503 new_mode = OVERSCROLL_NONE;
504 }
505
Mohsen Izadi93faac12017-07-29 04:45:15506 // The vertical overscroll is used for pull-to-refresh. Enable it only if
507 // pull-to-refresh is enabled.
Mohsen Izadi8c59ba52018-04-12 18:52:01508 if (new_mode == OVERSCROLL_SOUTH || new_mode == OVERSCROLL_NORTH) {
509 auto ptr_mode = OverscrollConfig::GetPullToRefreshMode();
510 if (ptr_mode == OverscrollConfig::PullToRefreshMode::kDisabled ||
511 (ptr_mode ==
512 OverscrollConfig::PullToRefreshMode::kEnabledTouchschreen &&
Mohsen Izadi5a6583512018-04-23 18:31:19513 is_touchpad) ||
Daniel Cheng224569ee2018-04-25 05:45:06514 time_since_last_ignored_scroll_ < kPullToRefreshCoolOffDelay) {
Mohsen Izadi5a6583512018-04-23 18:31:19515 overscroll_ignored_ = true;
Mohsen Izadi8c59ba52018-04-12 18:52:01516 new_mode = OVERSCROLL_NONE;
517 }
518 }
[email protected]3aad43b2013-11-05 20:20:18519
mfomitchev09f0d64a2017-03-02 19:40:07520 if (overscroll_mode_ == OVERSCROLL_NONE) {
521 SetOverscrollMode(new_mode, is_touchpad ? OverscrollSource::TOUCHPAD
522 : OverscrollSource::TOUCHSCREEN);
523 } else if (new_mode != overscroll_mode_) {
524 SetOverscrollMode(OVERSCROLL_NONE, OverscrollSource::NONE);
525 }
[email protected]fa6db252013-07-26 03:38:20526
527 if (overscroll_mode_ == OVERSCROLL_NONE)
[email protected]bfab3d02014-08-20 03:16:55528 return false;
[email protected]b4df9df2012-11-16 01:58:58529
Mohsen Izadi8f43c2c52018-10-09 17:17:11530 overscroll_ignored_ = false;
531
[email protected]b4df9df2012-11-16 01:58:58532 // Tell the delegate about the overscroll update so that it can update
533 // the display accordingly (e.g. show history preview etc.).
[email protected]9f4f47e2012-11-20 02:21:43534 if (delegate_) {
535 // Do not include the threshold amount when sending the deltas to the
536 // delegate.
537 float delegate_delta_x = overscroll_delta_x_;
Mohsen Izadi4ef8db72017-10-31 00:08:42538 if (fabs(delegate_delta_x) > start_threshold) {
[email protected]9f4f47e2012-11-20 02:21:43539 if (delegate_delta_x < 0)
Mohsen Izadi4ef8db72017-10-31 00:08:42540 delegate_delta_x += start_threshold;
[email protected]9f4f47e2012-11-20 02:21:43541 else
Mohsen Izadi4ef8db72017-10-31 00:08:42542 delegate_delta_x -= start_threshold;
[email protected]9f4f47e2012-11-20 02:21:43543 } else {
544 delegate_delta_x = 0.f;
545 }
546
547 float delegate_delta_y = overscroll_delta_y_;
Mohsen Izadi4ef8db72017-10-31 00:08:42548 if (fabs(delegate_delta_y) > start_threshold) {
[email protected]9f4f47e2012-11-20 02:21:43549 if (delegate_delta_y < 0)
Mohsen Izadi4ef8db72017-10-31 00:08:42550 delegate_delta_y += start_threshold;
[email protected]9f4f47e2012-11-20 02:21:43551 else
Mohsen Izadi4ef8db72017-10-31 00:08:42552 delegate_delta_y -= start_threshold;
[email protected]9f4f47e2012-11-20 02:21:43553 } else {
554 delegate_delta_y = 0.f;
555 }
[email protected]bfab3d02014-08-20 03:16:55556 return delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
[email protected]9f4f47e2012-11-20 02:21:43557 }
[email protected]bfab3d02014-08-20 03:16:55558 return false;
[email protected]b4df9df2012-11-16 01:58:58559}
560
561void OverscrollController::CompleteAction() {
chaopeng1d469b02018-04-05 15:41:07562 ignore_following_inertial_events_ = true;
[email protected]b4df9df2012-11-16 01:58:58563 if (delegate_)
564 delegate_->OnOverscrollComplete(overscroll_mode_);
Mohsen Izadif589f4b2017-08-24 02:55:48565 Reset();
[email protected]b4df9df2012-11-16 01:58:58566}
567
mfomitchev09f0d64a2017-03-02 19:40:07568void OverscrollController::SetOverscrollMode(OverscrollMode mode,
569 OverscrollSource source) {
[email protected]b4df9df2012-11-16 01:58:58570 if (overscroll_mode_ == mode)
571 return;
mfomitchev09f0d64a2017-03-02 19:40:07572
573 // If the mode changes to NONE, source is also NONE.
574 DCHECK(mode != OVERSCROLL_NONE || source == OverscrollSource::NONE);
575
Mohsen Izadif589f4b2017-08-24 02:55:48576 // When setting to a non-NONE mode and there is a locked mode, don't set the
577 // mode if the new mode is not the same as the locked mode.
578 if (mode != OVERSCROLL_NONE && locked_mode_ != OVERSCROLL_NONE &&
579 mode != locked_mode_) {
580 return;
581 }
582
[email protected]b4df9df2012-11-16 01:58:58583 OverscrollMode old_mode = overscroll_mode_;
584 overscroll_mode_ = mode;
Mohsen Izadi38795d5d2017-06-30 17:10:48585 overscroll_source_ = source;
Mohsen Izadif589f4b2017-08-24 02:55:48586 if (overscroll_mode_ == OVERSCROLL_NONE) {
[email protected]b4df9df2012-11-16 01:58:58587 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
Mohsen Izadif589f4b2017-08-24 02:55:48588 } else {
Mohsen Izadi9830436c2017-12-21 18:02:46589 scroll_state_ = ScrollState::OVERSCROLLING;
Mohsen Izadif589f4b2017-08-24 02:55:48590 locked_mode_ = overscroll_mode_;
591 }
Mohsen Izadi80189412018-03-22 17:10:18592 if (delegate_) {
593 delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_, source,
594 behavior_);
595 }
[email protected]b4df9df2012-11-16 01:58:58596}
597
Mohsen Izadi50180f392017-09-08 19:29:12598void OverscrollController::ResetScrollState() {
Mohsen Izadi9830436c2017-12-21 18:02:46599 scroll_state_ = ScrollState::NONE;
Mohsen Izadi50180f392017-09-08 19:29:12600 locked_mode_ = OVERSCROLL_NONE;
601}
602
[email protected]b4df9df2012-11-16 01:58:58603} // namespace content