1 // Copyright 2014 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.
5 #include "cc/input/input_handler.h"
6 #include "content/renderer/input/input_scroll_elasticity_controller.h"
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/WebKit/public/web/WebInputEvent.h"
14 PhaseNone
= blink::WebMouseWheelEvent::PhaseNone
,
15 PhaseBegan
= blink::WebMouseWheelEvent::PhaseBegan
,
16 PhaseStationary
= blink::WebMouseWheelEvent::PhaseStationary
,
17 PhaseChanged
= blink::WebMouseWheelEvent::PhaseChanged
,
18 PhaseEnded
= blink::WebMouseWheelEvent::PhaseEnded
,
19 PhaseCancelled
= blink::WebMouseWheelEvent::PhaseCancelled
,
20 PhaseMayBegin
= blink::WebMouseWheelEvent::PhaseMayBegin
,
23 class MockScrollElasticityHelper
: public cc::ScrollElasticityHelper
{
25 MockScrollElasticityHelper()
26 : set_stretch_amount_count_(0),
27 request_animate_count_(0) {}
28 ~MockScrollElasticityHelper() override
{}
30 // cc::ScrollElasticityHelper implementation:
31 gfx::Vector2dF
StretchAmount() const override
{ return stretch_amount_
; }
32 void SetStretchAmount(const gfx::Vector2dF
& stretch_amount
) override
{
33 set_stretch_amount_count_
+= 1;
34 stretch_amount_
= stretch_amount
;
36 gfx::ScrollOffset
ScrollOffset() const override
{ return scroll_offset_
; }
37 gfx::ScrollOffset
MaxScrollOffset() const override
{
38 return max_scroll_offset_
;
40 void ScrollBy(const gfx::Vector2dF
& delta
) override
{
41 scroll_offset_
+= gfx::ScrollOffset(delta
);
43 void RequestAnimate() override
{ request_animate_count_
+= 1; }
45 // Counters for number of times functions were called.
46 int request_animate_count() const { return request_animate_count_
; }
47 int set_stretch_amount_count() const { return set_stretch_amount_count_
; }
49 void SetScrollOffsetAndMaxScrollOffset(
50 const gfx::ScrollOffset
& scroll_offset
,
51 const gfx::ScrollOffset
& max_scroll_offset
) {
52 scroll_offset_
= scroll_offset
;
53 max_scroll_offset_
= max_scroll_offset
;
57 gfx::Vector2dF stretch_amount_
;
58 int set_stretch_amount_count_
;
59 int request_animate_count_
;
61 gfx::ScrollOffset scroll_offset_
;
62 gfx::ScrollOffset max_scroll_offset_
;
65 class ScrollElasticityControllerTest
: public testing::Test
{
67 ScrollElasticityControllerTest()
68 : controller_(&helper_
),
69 input_event_count_(0),
70 current_time_(base::TimeTicks::FromInternalValue(100000000ull)) {}
71 ~ScrollElasticityControllerTest() override
{}
73 void SendMouseWheelEvent(
76 const gfx::Vector2dF
& event_delta
= gfx::Vector2dF(),
77 const gfx::Vector2dF
& overscroll_delta
= gfx::Vector2dF()) {
78 blink::WebMouseWheelEvent event
;
79 event
.phase
= static_cast<blink::WebMouseWheelEvent::Phase
>(phase
);
81 static_cast<blink::WebMouseWheelEvent::Phase
>(momentum_phase
);
82 event
.deltaX
= -event_delta
.x();
83 event
.deltaY
= -event_delta
.y();
85 event
.timeStampSeconds
= (current_time_
- base::TimeTicks()).InSecondsF();
87 cc::InputHandlerScrollResult scroll_result
;
88 scroll_result
.did_overscroll_root
= !overscroll_delta
.IsZero();
89 scroll_result
.unused_scroll_delta
= overscroll_delta
;
91 controller_
.ObserveWheelEventAndResult(event
, scroll_result
);
92 input_event_count_
+= 1;
95 const base::TimeTicks
& TickCurrentTime() {
96 current_time_
+= base::TimeDelta::FromSecondsD(1 / 60.f
);
99 void TickCurrentTimeAndAnimate() {
101 controller_
.Animate(current_time_
);
104 MockScrollElasticityHelper helper_
;
105 InputScrollElasticityController controller_
;
106 int input_event_count_
;
107 base::TimeTicks current_time_
;
110 // Verify that stretching only occurs in one axis at a time, and that it
111 // is biased to the Y axis.
112 TEST_F(ScrollElasticityControllerTest
, Axis
) {
113 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 0),
114 gfx::ScrollOffset(0, 0));
116 // If we push equally in the X and Y directions, we should see a stretch only
117 // in the Y direction.
118 SendMouseWheelEvent(PhaseBegan
, PhaseNone
);
119 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(10, 10),
120 gfx::Vector2dF(10, 10));
121 EXPECT_EQ(1, helper_
.set_stretch_amount_count());
122 EXPECT_EQ(0.f
, helper_
.StretchAmount().x());
123 EXPECT_LT(0.f
, helper_
.StretchAmount().y());
124 helper_
.SetStretchAmount(gfx::Vector2dF());
125 EXPECT_EQ(2, helper_
.set_stretch_amount_count());
126 SendMouseWheelEvent(PhaseEnded
, PhaseNone
);
127 EXPECT_EQ(0, helper_
.request_animate_count());
129 // If we push more in the X direction than the Y direction, we should see a
130 // stretch only in the X direction. This decision should be based on the
131 // input delta, not the actual overscroll delta.
132 SendMouseWheelEvent(PhaseBegan
, PhaseNone
);
133 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(-25, 10),
134 gfx::Vector2dF(-25, 40));
135 EXPECT_EQ(3, helper_
.set_stretch_amount_count());
136 EXPECT_GT(0.f
, helper_
.StretchAmount().x());
137 EXPECT_EQ(0.f
, helper_
.StretchAmount().y());
138 helper_
.SetStretchAmount(gfx::Vector2dF());
139 EXPECT_EQ(4, helper_
.set_stretch_amount_count());
140 SendMouseWheelEvent(PhaseEnded
, PhaseNone
);
141 EXPECT_EQ(0, helper_
.request_animate_count());
144 // Verify that we need a total overscroll delta of at least 10 in a pinned
145 // direction before we start stretching.
146 TEST_F(ScrollElasticityControllerTest
, MinimumDeltaBeforeStretch
) {
147 // We should not start stretching while we are not pinned in the direction
148 // of the scroll (even if there is an overscroll delta). We have to wait for
149 // the regular scroll to eat all of the events.
150 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5),
151 gfx::ScrollOffset(10, 10));
152 SendMouseWheelEvent(PhaseMayBegin
, PhaseNone
);
153 SendMouseWheelEvent(PhaseBegan
, PhaseNone
);
154 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, 10),
155 gfx::Vector2dF(0, 10));
156 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, 10),
157 gfx::Vector2dF(0, 10));
158 EXPECT_EQ(0, helper_
.set_stretch_amount_count());
160 // Now pin the -X and +Y direction. The first event will not generate a
162 // because it is below the delta threshold of 10.
163 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(0, 10),
164 gfx::ScrollOffset(10, 10));
165 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, 10),
166 gfx::Vector2dF(0, 8));
167 EXPECT_EQ(0, helper_
.set_stretch_amount_count());
169 // Make the next scroll be in the -X direction more than the +Y direction,
170 // which will erase the memory of the previous unused delta of 8.
171 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(-10, 5),
172 gfx::Vector2dF(-8, 10));
173 EXPECT_EQ(0, helper_
.set_stretch_amount_count());
175 // Now push against the pinned +Y direction again by 8. We reset the
176 // previous delta, so this will not generate a stretch.
177 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, 10),
178 gfx::Vector2dF(0, 8));
179 EXPECT_EQ(0, helper_
.set_stretch_amount_count());
181 // Push against +Y by another 8. This gets us above the delta threshold of
182 // 10, so we should now have had the stretch set, and it should be in the
183 // +Y direction. The scroll in the -X direction should have been forgotten.
184 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, 10),
185 gfx::Vector2dF(0, 8));
186 EXPECT_EQ(1, helper_
.set_stretch_amount_count());
187 EXPECT_EQ(0.f
, helper_
.StretchAmount().x());
188 EXPECT_LT(0.f
, helper_
.StretchAmount().y());
190 // End the gesture. Because there is a non-zero stretch, we should be in the
191 // animated state, and should have had a frame requested.
192 EXPECT_EQ(0, helper_
.request_animate_count());
193 SendMouseWheelEvent(PhaseEnded
, PhaseNone
);
194 EXPECT_EQ(1, helper_
.request_animate_count());
197 // Verify that an stretch caused by a momentum scroll will switch to the
198 // animating mode, where input events are ignored, and the stretch is updated
200 TEST_F(ScrollElasticityControllerTest
, MomentumAnimate
) {
201 // Do an active scroll, then switch to the momentum phase and scroll for a
203 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5),
204 gfx::ScrollOffset(10, 10));
205 SendMouseWheelEvent(PhaseBegan
, PhaseNone
);
206 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, -80),
207 gfx::Vector2dF(0, 0));
208 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, -80),
209 gfx::Vector2dF(0, 0));
210 SendMouseWheelEvent(PhaseChanged
, PhaseNone
, gfx::Vector2dF(0, -80),
211 gfx::Vector2dF(0, 0));
212 SendMouseWheelEvent(PhaseEnded
, PhaseNone
);
213 SendMouseWheelEvent(PhaseNone
, PhaseBegan
);
214 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
215 gfx::Vector2dF(0, 0));
216 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
217 gfx::Vector2dF(0, 0));
218 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
219 gfx::Vector2dF(0, 0));
220 EXPECT_EQ(0, helper_
.set_stretch_amount_count());
222 // Hit the -Y edge and overscroll slightly, but not enough to go over the
223 // threshold to cause a stretch.
224 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 0),
225 gfx::ScrollOffset(10, 10));
226 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
227 gfx::Vector2dF(0, -8));
228 EXPECT_EQ(0, helper_
.set_stretch_amount_count());
229 EXPECT_EQ(0, helper_
.request_animate_count());
231 // Take another step, this time going over the threshold. This should update
232 // the stretch amount, and then switch to the animating mode.
233 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
234 gfx::Vector2dF(0, -80));
235 EXPECT_EQ(1, helper_
.set_stretch_amount_count());
236 EXPECT_EQ(1, helper_
.request_animate_count());
237 EXPECT_GT(-1.f
, helper_
.StretchAmount().y());
239 // Subsequent momentum events should do nothing.
240 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
241 gfx::Vector2dF(0, -80));
242 SendMouseWheelEvent(PhaseNone
, PhaseChanged
, gfx::Vector2dF(0, -80),
243 gfx::Vector2dF(0, -80));
244 SendMouseWheelEvent(PhaseNone
, PhaseEnded
, gfx::Vector2dF(0, -80),
245 gfx::Vector2dF(0, -80));
246 EXPECT_EQ(1, helper_
.set_stretch_amount_count());
247 EXPECT_EQ(1, helper_
.request_animate_count());
249 // Subsequent animate events should update the stretch amount and request
251 TickCurrentTimeAndAnimate();
252 EXPECT_EQ(2, helper_
.set_stretch_amount_count());
253 EXPECT_EQ(2, helper_
.request_animate_count());
254 EXPECT_GT(-1.f
, helper_
.StretchAmount().y());
256 // Touching the trackpad (a PhaseMayBegin event) should disable animation.
257 SendMouseWheelEvent(PhaseMayBegin
, PhaseNone
);
258 TickCurrentTimeAndAnimate();
259 EXPECT_EQ(2, helper_
.set_stretch_amount_count());
260 EXPECT_EQ(2, helper_
.request_animate_count());
262 // Releasing the trackpad should re-enable animation.
263 SendMouseWheelEvent(PhaseCancelled
, PhaseNone
);
264 EXPECT_EQ(2, helper_
.set_stretch_amount_count());
265 EXPECT_EQ(3, helper_
.request_animate_count());
266 TickCurrentTimeAndAnimate();
267 EXPECT_EQ(3, helper_
.set_stretch_amount_count());
268 EXPECT_EQ(4, helper_
.request_animate_count());
270 // Keep animating frames until the stretch returns to rest.
271 int stretch_count
= 3;
272 int animate_count
= 4;
274 TickCurrentTimeAndAnimate();
275 if (helper_
.StretchAmount().IsZero()) {
277 EXPECT_EQ(stretch_count
, helper_
.set_stretch_amount_count());
278 EXPECT_EQ(animate_count
, helper_
.request_animate_count());
283 EXPECT_EQ(stretch_count
, helper_
.set_stretch_amount_count());
284 EXPECT_EQ(animate_count
, helper_
.request_animate_count());
287 // After coming to rest, no subsequent animate calls change anything.
288 TickCurrentTimeAndAnimate();
289 EXPECT_EQ(stretch_count
, helper_
.set_stretch_amount_count());
290 EXPECT_EQ(animate_count
, helper_
.request_animate_count());
293 // Verify that an stretch opposing a scroll is correctly resolved.
294 TEST_F(ScrollElasticityControllerTest
, ReconcileStretchAndScroll
) {
295 SendMouseWheelEvent(PhaseBegan
, PhaseNone
);
297 // Verify completely knocking out the scroll in the -Y direction.
298 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5),
299 gfx::ScrollOffset(10, 10));
300 helper_
.SetStretchAmount(gfx::Vector2dF(0, -10));
301 controller_
.ReconcileStretchAndScroll();
302 EXPECT_EQ(helper_
.StretchAmount(), gfx::Vector2dF(0, -5));
303 EXPECT_EQ(helper_
.ScrollOffset(), gfx::ScrollOffset(5, 0));
305 // Verify partially knocking out the scroll in the -Y direction.
306 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 8),
307 gfx::ScrollOffset(10, 10));
308 helper_
.SetStretchAmount(gfx::Vector2dF(0, -5));
309 controller_
.ReconcileStretchAndScroll();
310 EXPECT_EQ(helper_
.StretchAmount(), gfx::Vector2dF(0, 0));
311 EXPECT_EQ(helper_
.ScrollOffset(), gfx::ScrollOffset(5, 3));
313 // Verify completely knocking out the scroll in the +X direction.
314 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(5, 5),
315 gfx::ScrollOffset(10, 10));
316 helper_
.SetStretchAmount(gfx::Vector2dF(10, 0));
317 controller_
.ReconcileStretchAndScroll();
318 EXPECT_EQ(helper_
.StretchAmount(), gfx::Vector2dF(5, 0));
319 EXPECT_EQ(helper_
.ScrollOffset(), gfx::ScrollOffset(10, 5));
321 // Verify partially knocking out the scroll in the +X and +Y directions.
322 helper_
.SetScrollOffsetAndMaxScrollOffset(gfx::ScrollOffset(2, 3),
323 gfx::ScrollOffset(10, 10));
324 helper_
.SetStretchAmount(gfx::Vector2dF(5, 5));
325 controller_
.ReconcileStretchAndScroll();
326 EXPECT_EQ(helper_
.StretchAmount(), gfx::Vector2dF(0, 0));
327 EXPECT_EQ(helper_
.ScrollOffset(), gfx::ScrollOffset(7, 8));
331 } // namespace content