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
5package org.chromium.android_webview.test;
6
7import android.graphics.Rect;
8import android.test.InstrumentationTestCase;
9import android.test.suitebuilder.annotation.SmallTest;
10import android.widget.OverScroller;
11
12import org.chromium.android_webview.AwScrollOffsetManager;
13import org.chromium.base.test.util.Feature;
14
15public class AwScrollOffsetManagerTest extends InstrumentationTestCase {
16    private static class TestScrollOffsetManagerDelegate implements AwScrollOffsetManager.Delegate {
17        private int mOverScrollDeltaX;
18        private int mOverScrollDeltaY;
19        private int mOverScrollCallCount;
20        private int mScrollX;
21        private int mScrollY;
22        private int mNativeScrollX;
23        private int mNativeScrollY;
24        private int mInvalidateCount;
25
26        public int getOverScrollDeltaX() {
27            return mOverScrollDeltaX;
28        }
29
30        public int getOverScrollDeltaY() {
31            return mOverScrollDeltaY;
32        }
33
34        public int getOverScrollCallCount() {
35            return mOverScrollCallCount;
36        }
37
38        public int getScrollX() {
39            return mScrollX;
40        }
41
42        public int getScrollY() {
43            return mScrollY;
44        }
45
46        public int getNativeScrollX() {
47            return mNativeScrollX;
48        }
49
50        public int getNativeScrollY() {
51            return mNativeScrollY;
52        }
53
54        public int getInvalidateCount() {
55            return mInvalidateCount;
56        }
57
58        @Override
59        public void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY,
60                int scrollRangeX, int scrollRangeY, boolean isTouchEvent) {
61            mOverScrollDeltaX = deltaX;
62            mOverScrollDeltaY = deltaY;
63            mOverScrollCallCount += 1;
64        }
65
66        @Override
67        public void scrollContainerViewTo(int x, int y) {
68            mScrollX = x;
69            mScrollY = y;
70        }
71
72        @Override
73        public void scrollNativeTo(int x, int y) {
74            mNativeScrollX = x;
75            mNativeScrollY = y;
76        }
77
78        @Override
79        public int getContainerViewScrollX() {
80            return mScrollX;
81        }
82
83        @Override
84        public int getContainerViewScrollY() {
85            return mScrollY;
86        }
87
88        @Override
89        public void invalidate() {
90            mInvalidateCount += 1;
91        }
92    }
93
94    private void simulateScrolling(AwScrollOffsetManager offsetManager,
95            TestScrollOffsetManagerDelegate delegate, int scrollX, int scrollY) {
96        // Scrolling is a two-phase action. First we ask the manager to scroll
97        int callCount = delegate.getOverScrollCallCount();
98        offsetManager.scrollContainerViewTo(scrollX, scrollY);
99        // The manager then asks the delegate to overscroll the view.
100        assertEquals(callCount + 1, delegate.getOverScrollCallCount());
101        assertEquals(scrollX, delegate.getOverScrollDeltaX() + delegate.getScrollX());
102        assertEquals(scrollY, delegate.getOverScrollDeltaY() + delegate.getScrollY());
103        // As a response to that the menager expects the view to call back with the new scroll.
104        offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false);
105    }
106
107    private void simlateOverScrollPropagation(AwScrollOffsetManager offsetManager,
108            TestScrollOffsetManagerDelegate delegate) {
109        assertTrue(delegate.getOverScrollCallCount() > 0);
110
111        offsetManager.onContainerViewOverScrolled(
112                delegate.getOverScrollDeltaX() + delegate.getScrollX(),
113                delegate.getOverScrollDeltaY() + delegate.getScrollY(), false, false);
114    }
115
116    @SmallTest
117    @Feature({"AndroidWebView"})
118    public void testWhenContentSizeMatchesView() {
119        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
120        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
121        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
122
123        final int width = 132;
124        final int height = 212;
125        final int scrollX = 11;
126        final int scrollY = 13;
127
128        offsetManager.setMaxScrollOffset(0, 0);
129        offsetManager.setContainerViewSize(width, height);
130
131        assertEquals(width, offsetManager.computeHorizontalScrollRange());
132        assertEquals(height, offsetManager.computeVerticalScrollRange());
133
134        // Since the view size and contents size are equal no scrolling should be possible.
135        assertEquals(0, offsetManager.computeMaximumHorizontalScrollOffset());
136        assertEquals(0, offsetManager.computeMaximumVerticalScrollOffset());
137
138        // Scrolling should generate overscroll but not update the scroll offset.
139        simulateScrolling(offsetManager, delegate, scrollX, scrollY);
140        assertEquals(scrollX, delegate.getOverScrollDeltaX());
141        assertEquals(scrollY, delegate.getOverScrollDeltaY());
142        assertEquals(0, delegate.getScrollX());
143        assertEquals(0, delegate.getScrollY());
144        assertEquals(0, delegate.getNativeScrollX());
145        assertEquals(0, delegate.getNativeScrollY());
146
147        // Scrolling to 0,0 should result in no deltas.
148        simulateScrolling(offsetManager, delegate, 0, 0);
149        assertEquals(0, delegate.getOverScrollDeltaX());
150        assertEquals(0, delegate.getOverScrollDeltaY());
151
152        // Negative scrolling should result in negative deltas but no scroll offset update.
153        simulateScrolling(offsetManager, delegate, -scrollX, -scrollY);
154        assertEquals(-scrollX, delegate.getOverScrollDeltaX());
155        assertEquals(-scrollY, delegate.getOverScrollDeltaY());
156        assertEquals(0, delegate.getScrollX());
157        assertEquals(0, delegate.getScrollY());
158        assertEquals(0, delegate.getNativeScrollX());
159        assertEquals(0, delegate.getNativeScrollY());
160    }
161
162    private static final int VIEW_WIDTH = 211;
163    private static final int VIEW_HEIGHT = 312;
164    private static final int MAX_HORIZONTAL_OFFSET = 757;
165    private static final int MAX_VERTICAL_OFFSET = 127;
166    private static final int CONTENT_WIDTH = VIEW_WIDTH + MAX_HORIZONTAL_OFFSET;
167    private static final int CONTENT_HEIGHT = VIEW_HEIGHT + MAX_VERTICAL_OFFSET;
168
169    @SmallTest
170    @Feature({"AndroidWebView"})
171    public void testScrollRangeAndMaxOffset() {
172        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
173        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
174        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
175
176        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
177        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
178
179        assertEquals(CONTENT_WIDTH, offsetManager.computeHorizontalScrollRange());
180        assertEquals(CONTENT_HEIGHT, offsetManager.computeVerticalScrollRange());
181
182        assertEquals(MAX_HORIZONTAL_OFFSET, offsetManager.computeMaximumHorizontalScrollOffset());
183        assertEquals(MAX_VERTICAL_OFFSET, offsetManager.computeMaximumVerticalScrollOffset());
184
185        // Scrolling beyond the maximum should be clamped.
186        final int scrollX = MAX_HORIZONTAL_OFFSET + 10;
187        final int scrollY = MAX_VERTICAL_OFFSET + 11;
188
189        simulateScrolling(offsetManager, delegate, scrollX, scrollY);
190        assertEquals(scrollX, delegate.getOverScrollDeltaX());
191        assertEquals(scrollY, delegate.getOverScrollDeltaY());
192        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX());
193        assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY());
194        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX());
195        assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY());
196
197        // Scrolling to negative coordinates should be clamped back to 0,0.
198        simulateScrolling(offsetManager, delegate, -scrollX, -scrollY);
199        assertEquals(0, delegate.getScrollX());
200        assertEquals(0, delegate.getScrollY());
201        assertEquals(0, delegate.getNativeScrollX());
202        assertEquals(0, delegate.getNativeScrollY());
203
204        // The onScrollChanged method is callable by third party code and should also be clamped
205        offsetManager.onContainerViewScrollChanged(scrollX, scrollY);
206        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX());
207        assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY());
208
209        offsetManager.onContainerViewScrollChanged(-scrollX, -scrollY);
210        assertEquals(0, delegate.getNativeScrollX());
211        assertEquals(0, delegate.getNativeScrollY());
212    }
213
214    @SmallTest
215    @Feature({"AndroidWebView"})
216    public void testDelegateCanOverrideScroll() {
217        final int overrideScrollX = 10;
218        final int overrideScrollY = 10;
219
220        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() {
221            @Override
222            public int getContainerViewScrollX() {
223                return overrideScrollX;
224            }
225
226            @Override
227            public int getContainerViewScrollY() {
228                return overrideScrollY;
229            }
230        };
231        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
232        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
233
234        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
235        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
236
237        offsetManager.onContainerViewOverScrolled(0, 0, false, false);
238        assertEquals(overrideScrollX, delegate.getNativeScrollX());
239        assertEquals(overrideScrollY, delegate.getNativeScrollY());
240    }
241
242    @SmallTest
243    @Feature({"AndroidWebView"})
244    public void testDelegateOverridenScrollsDontExceedBounds() {
245        final int overrideScrollX = MAX_HORIZONTAL_OFFSET + 10;
246        final int overrideScrollY = MAX_VERTICAL_OFFSET + 20;
247        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() {
248            @Override
249            public int getContainerViewScrollX() {
250                return overrideScrollX;
251            }
252
253            @Override
254            public int getContainerViewScrollY() {
255                return overrideScrollY;
256            }
257        };
258        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
259        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
260
261        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
262        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
263
264        offsetManager.onContainerViewOverScrolled(0, 0, false, false);
265        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX());
266        assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY());
267    }
268
269    @SmallTest
270    @Feature({"AndroidWebView"})
271    public void testScrollContainerViewTo() {
272        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
273        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
274        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
275
276        final int scrollX = 31;
277        final int scrollY = 41;
278
279        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
280        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
281
282        assertEquals(0, delegate.getOverScrollDeltaX());
283        assertEquals(0, delegate.getOverScrollDeltaY());
284        int callCount = delegate.getOverScrollCallCount();
285
286        offsetManager.scrollContainerViewTo(scrollX, scrollY);
287        assertEquals(callCount + 1, delegate.getOverScrollCallCount());
288        assertEquals(scrollX, delegate.getOverScrollDeltaX());
289        assertEquals(scrollY, delegate.getOverScrollDeltaY());
290    }
291
292    @SmallTest
293    @Feature({"AndroidWebView"})
294    public void testOnContainerViewOverScrolled() {
295        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
296        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
297        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
298
299        final int scrollX = 31;
300        final int scrollY = 41;
301
302        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
303        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
304
305        assertEquals(0, delegate.getScrollX());
306        assertEquals(0, delegate.getScrollY());
307        assertEquals(0, delegate.getNativeScrollX());
308        assertEquals(0, delegate.getNativeScrollY());
309
310        offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false);
311        assertEquals(scrollX, delegate.getScrollX());
312        assertEquals(scrollY, delegate.getScrollY());
313        assertEquals(scrollX, delegate.getNativeScrollX());
314        assertEquals(scrollY, delegate.getNativeScrollY());
315    }
316
317    @SmallTest
318    @Feature({"AndroidWebView"})
319    public void testDefersScrollUntilTouchEnd() {
320        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
321        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
322        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
323
324        final int scrollX = 31;
325        final int scrollY = 41;
326
327        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
328        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
329
330        offsetManager.setProcessingTouchEvent(true);
331        offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false);
332        assertEquals(scrollX, delegate.getScrollX());
333        assertEquals(scrollY, delegate.getScrollY());
334        assertEquals(0, delegate.getNativeScrollX());
335        assertEquals(0, delegate.getNativeScrollY());
336
337        offsetManager.setProcessingTouchEvent(false);
338        assertEquals(scrollX, delegate.getScrollX());
339        assertEquals(scrollY, delegate.getScrollY());
340        assertEquals(scrollX, delegate.getNativeScrollX());
341        assertEquals(scrollY, delegate.getNativeScrollY());
342    }
343
344    @SmallTest
345    @Feature({"AndroidWebView"})
346    public void testFlingScroll() {
347        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
348        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
349        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
350
351        offsetManager.flingScroll(0, 101);
352        assertTrue(!scroller.isFinished());
353        assertTrue(delegate.getInvalidateCount() == 1);
354        assertEquals(101, (int) scroller.getCurrVelocity());
355
356        offsetManager.flingScroll(111, 0);
357        assertTrue(!scroller.isFinished());
358        assertTrue(delegate.getInvalidateCount() == 2);
359        assertEquals(111, (int) scroller.getCurrVelocity());
360    }
361
362    @SmallTest
363    @Feature({"AndroidWebView"})
364    public void testRequestChildRectangleOnScreenDontScrollIfAlreadyThere() {
365        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
366        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
367        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
368
369        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
370        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
371
372        offsetManager.requestChildRectangleOnScreen(0, 0,
373                new Rect(0, 0, VIEW_WIDTH / 4, VIEW_HEIGHT / 4), true);
374        assertEquals(0, delegate.getOverScrollDeltaX());
375        assertEquals(0, delegate.getOverScrollDeltaY());
376        assertEquals(0, delegate.getScrollX());
377        assertEquals(0, delegate.getScrollY());
378
379        offsetManager.requestChildRectangleOnScreen(3 * VIEW_WIDTH / 4, 3 * VIEW_HEIGHT / 4,
380                new Rect(0, 0, VIEW_WIDTH / 4, VIEW_HEIGHT / 4), true);
381        assertEquals(0, delegate.getOverScrollDeltaX());
382        assertEquals(0, delegate.getOverScrollDeltaY());
383        assertEquals(0, delegate.getScrollX());
384        assertEquals(0, delegate.getScrollY());
385    }
386
387    @SmallTest
388    @Feature({"AndroidWebView"})
389    public void testRequestChildRectangleOnScreenScrollToBottom() {
390        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
391        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
392        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
393
394        final int rectWidth = 2;
395        final int rectHeight = 3;
396
397        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
398        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
399
400        offsetManager.requestChildRectangleOnScreen(CONTENT_WIDTH - rectWidth,
401                CONTENT_HEIGHT - rectHeight, new Rect(0, 0, rectWidth, rectHeight), true);
402        simlateOverScrollPropagation(offsetManager, delegate);
403        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getOverScrollDeltaX());
404        assertEquals(CONTENT_HEIGHT - rectHeight - VIEW_HEIGHT / 3, delegate.getOverScrollDeltaY());
405        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX());
406        assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY());
407    }
408
409    @SmallTest
410    @Feature({"AndroidWebView"})
411    public void testRequestChildRectangleOnScreenScrollToBottomLargeRect() {
412        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
413        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
414        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
415
416        final int rectWidth = VIEW_WIDTH;
417        final int rectHeight = VIEW_HEIGHT;
418
419        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
420        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
421
422        offsetManager.requestChildRectangleOnScreen(CONTENT_WIDTH - rectWidth,
423                CONTENT_HEIGHT - rectHeight, new Rect(0, 0, rectWidth, rectHeight), true);
424        simlateOverScrollPropagation(offsetManager, delegate);
425        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getOverScrollDeltaX());
426        assertEquals(MAX_VERTICAL_OFFSET, delegate.getOverScrollDeltaY());
427        assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX());
428        assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY());
429    }
430
431    @SmallTest
432    @Feature({"AndroidWebView"})
433    public void testRequestChildRectangleOnScreenScrollToTop() {
434        TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate();
435        OverScroller scroller = new OverScroller(getInstrumentation().getContext());
436        AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate, scroller);
437
438        final int rectWidth = 2;
439        final int rectHeight = 3;
440
441        offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET);
442        offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT);
443        simulateScrolling(offsetManager, delegate,
444                CONTENT_WIDTH - VIEW_WIDTH, CONTENT_HEIGHT - VIEW_HEIGHT);
445
446        offsetManager.requestChildRectangleOnScreen(0, 0,
447                new Rect(0, 0, rectWidth, rectHeight), true);
448        simlateOverScrollPropagation(offsetManager, delegate);
449        assertEquals(-CONTENT_WIDTH + VIEW_WIDTH, delegate.getOverScrollDeltaX());
450        assertEquals(-CONTENT_HEIGHT + VIEW_HEIGHT, delegate.getOverScrollDeltaY());
451        assertEquals(0, delegate.getScrollX());
452        assertEquals(0, delegate.getScrollX());
453    }
454}
455