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.content.Context;
8import android.test.suitebuilder.annotation.SmallTest;
9import android.util.Log;
10import android.view.Gravity;
11import android.view.View;
12
13import org.chromium.android_webview.AwContents;
14import org.chromium.android_webview.AwContentsClient;
15import org.chromium.android_webview.test.util.AwTestTouchUtils;
16import org.chromium.android_webview.test.util.CommonResources;
17import org.chromium.android_webview.test.util.JavascriptEventObserver;
18import org.chromium.base.test.util.DisabledTest;
19import org.chromium.base.test.util.Feature;
20import org.chromium.content.browser.ContentViewCore;
21import org.chromium.content.browser.test.util.CallbackHelper;
22import org.chromium.content.browser.test.util.Criteria;
23import org.chromium.content.browser.test.util.CriteriaHelper;
24import org.chromium.ui.gfx.DeviceDisplayInfo;
25
26import java.util.concurrent.Callable;
27import java.util.concurrent.CountDownLatch;
28import java.util.concurrent.atomic.AtomicBoolean;
29
30/**
31 * Integration tests for synchronous scrolling.
32 */
33public class AndroidScrollIntegrationTest extends AwTestBase {
34    private static final int SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS = 6 * 1000;
35
36    private static class OverScrollByCallbackHelper extends CallbackHelper {
37        int mDeltaX;
38        int mDeltaY;
39
40        public int getDeltaX() {
41            assert getCallCount() > 0;
42            return mDeltaX;
43        }
44
45        public int getDeltaY() {
46            assert getCallCount() > 0;
47            return mDeltaY;
48        }
49
50        public void notifyCalled(int deltaX, int deltaY) {
51            mDeltaX = deltaX;
52            mDeltaY = deltaY;
53            notifyCalled();
54        }
55    }
56
57    private static class ScrollTestContainerView extends AwTestContainerView {
58        private int mMaxScrollXPix = -1;
59        private int mMaxScrollYPix = -1;
60
61        private CallbackHelper mOnScrollToCallbackHelper = new CallbackHelper();
62        private OverScrollByCallbackHelper mOverScrollByCallbackHelper =
63            new OverScrollByCallbackHelper();
64
65        public ScrollTestContainerView(Context context) {
66            super(context);
67        }
68
69        public CallbackHelper getOnScrollToCallbackHelper() {
70            return mOnScrollToCallbackHelper;
71        }
72
73        public OverScrollByCallbackHelper getOverScrollByCallbackHelper() {
74            return mOverScrollByCallbackHelper;
75        }
76
77        public void setMaxScrollX(int maxScrollXPix) {
78            mMaxScrollXPix = maxScrollXPix;
79        }
80
81        public void setMaxScrollY(int maxScrollYPix) {
82            mMaxScrollYPix = maxScrollYPix;
83        }
84
85        @Override
86        protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
87                     int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY,
88                     boolean isTouchEvent) {
89            mOverScrollByCallbackHelper.notifyCalled(deltaX, deltaY);
90            return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
91                     scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
92        }
93
94        @Override
95        public void scrollTo(int x, int y) {
96            if (mMaxScrollXPix != -1)
97                x = Math.min(mMaxScrollXPix, x);
98            if (mMaxScrollYPix != -1)
99                y = Math.min(mMaxScrollYPix, y);
100            super.scrollTo(x, y);
101            mOnScrollToCallbackHelper.notifyCalled();
102        }
103    }
104
105    @Override
106    protected TestDependencyFactory createTestDependencyFactory() {
107        return new TestDependencyFactory() {
108            @Override
109            public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) {
110                return new ScrollTestContainerView(activity);
111            }
112        };
113    }
114
115    private static final String TEST_PAGE_COMMON_HEADERS =
116        "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> " +
117        "<style type=\"text/css\"> " +
118        "   div { " +
119        "      width:1000px; " +
120        "      height:10000px; " +
121        "      background-color: blue; " +
122        "   } " +
123        "</style> ";
124    private static final String TEST_PAGE_COMMON_CONTENT = "<div>test div</div> ";
125
126    private String makeTestPage(String onscrollObserver, String firstFrameObserver,
127            String extraContent) {
128        String content = TEST_PAGE_COMMON_CONTENT + extraContent;
129        if (onscrollObserver != null) {
130            content +=
131            "<script> " +
132            "   window.onscroll = function(oEvent) { " +
133            "       " + onscrollObserver + ".notifyJava(); " +
134            "   } " +
135            "</script>";
136        }
137        if (firstFrameObserver != null) {
138            content +=
139            "<script> " +
140            "   window.framesToIgnore = 10; " +
141            "   window.onAnimationFrame = function(timestamp) { " +
142            "     if (window.framesToIgnore == 0) { " +
143            "         " + firstFrameObserver + ".notifyJava(); " +
144            "     } else {" +
145            "       window.framesToIgnore -= 1; " +
146            "       window.requestAnimationFrame(window.onAnimationFrame); " +
147            "     } " +
148            "   }; " +
149            "   window.requestAnimationFrame(window.onAnimationFrame); " +
150            "</script>";
151        }
152        return CommonResources.makeHtmlPageFrom(TEST_PAGE_COMMON_HEADERS, content);
153    }
154
155    private void scrollToOnMainSync(final View view, final int xPix, final int yPix) {
156        getInstrumentation().runOnMainSync(new Runnable() {
157            @Override
158            public void run() {
159                view.scrollTo(xPix, yPix);
160            }
161        });
162    }
163
164    private void setMaxScrollOnMainSync(final ScrollTestContainerView testContainerView,
165            final int maxScrollXPix, final int maxScrollYPix) {
166        getInstrumentation().runOnMainSync(new Runnable() {
167            @Override
168            public void run() {
169                testContainerView.setMaxScrollX(maxScrollXPix);
170                testContainerView.setMaxScrollY(maxScrollYPix);
171            }
172        });
173    }
174
175    private boolean checkScrollOnMainSync(final ScrollTestContainerView testContainerView,
176            final int scrollXPix, final int scrollYPix) {
177        final AtomicBoolean equal = new AtomicBoolean(false);
178        getInstrumentation().runOnMainSync(new Runnable() {
179            @Override
180            public void run() {
181                equal.set((scrollXPix == testContainerView.getScrollX()) &&
182                    (scrollYPix == testContainerView.getScrollY()));
183            }
184        });
185        return equal.get();
186    }
187
188    private void assertScrollOnMainSync(final ScrollTestContainerView testContainerView,
189            final int scrollXPix, final int scrollYPix) {
190        getInstrumentation().runOnMainSync(new Runnable() {
191            @Override
192            public void run() {
193                assertEquals(scrollXPix, testContainerView.getScrollX());
194                assertEquals(scrollYPix, testContainerView.getScrollY());
195            }
196        });
197    }
198
199    private void assertScrollInJs(final AwContents awContents,
200            final TestAwContentsClient contentsClient,
201            final int xCss, final int yCss) throws Exception {
202        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
203                @Override
204                public boolean isSatisfied() {
205                    try {
206                        String x = executeJavaScriptAndWaitForResult(awContents, contentsClient,
207                            "window.scrollX");
208                        String y = executeJavaScriptAndWaitForResult(awContents, contentsClient,
209                            "window.scrollY");
210                        return (Integer.toString(xCss).equals(x) &&
211                            Integer.toString(yCss).equals(y));
212                    } catch (Throwable t) {
213                        t.printStackTrace();
214                        fail("Failed to get window.scroll(X/Y): " + t.toString());
215                        return false;
216                    }
217                }
218            }, WAIT_TIMEOUT_SECONDS * 1000, CHECK_INTERVAL));
219    }
220
221    private void assertScrolledToBottomInJs(final AwContents awContents,
222            final TestAwContentsClient contentsClient) throws Exception {
223        final String isBottomScript = "window.scrollY == " +
224            "(window.document.documentElement.scrollHeight - window.innerHeight)";
225        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
226                @Override
227                public boolean isSatisfied() {
228                    try {
229                        String r = executeJavaScriptAndWaitForResult(awContents, contentsClient,
230                            isBottomScript);
231                        return r.equals("true");
232                    } catch (Throwable t) {
233                        t.printStackTrace();
234                        fail("Failed to get window.scroll(X/Y): " + t.toString());
235                        return false;
236                    }
237                }
238            }, WAIT_TIMEOUT_SECONDS * 1000, CHECK_INTERVAL));
239    }
240
241    private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView,
242            final TestAwContentsClient contentsClient,
243            final String onscrollObserverName, final String extraContent) throws Exception {
244        final JavascriptEventObserver firstFrameObserver = new JavascriptEventObserver();
245        final String firstFrameObserverName = "firstFrameObserver";
246        enableJavaScriptOnUiThread(testContainerView.getAwContents());
247
248        getInstrumentation().runOnMainSync(new Runnable() {
249            @Override
250            public void run() {
251                firstFrameObserver.register(testContainerView.getContentViewCore(),
252                    firstFrameObserverName);
253            }
254        });
255
256        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
257                makeTestPage(onscrollObserverName, firstFrameObserverName, extraContent),
258                "text/html", false);
259
260        // We wait for "a couple" of frames for the active tree in CC to stabilize and for pending
261        // tree activations to stop clobbering the root scroll layer's scroll offset. This wait
262        // doesn't strictly guarantee that but there isn't a good alternative and this seems to
263        // work fine.
264        firstFrameObserver.waitForEvent(WAIT_TIMEOUT_SECONDS * 1000);
265    }
266
267    @SmallTest
268    @Feature({"AndroidWebView"})
269    public void testUiScrollReflectedInJs() throws Throwable {
270        final TestAwContentsClient contentsClient = new TestAwContentsClient();
271        final ScrollTestContainerView testContainerView =
272            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
273        enableJavaScriptOnUiThread(testContainerView.getAwContents());
274
275        final double deviceDIPScale =
276            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
277        final int targetScrollXCss = 233;
278        final int targetScrollYCss = 322;
279        final int targetScrollXPix = (int) Math.ceil(targetScrollXCss * deviceDIPScale);
280        final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);
281        final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver();
282
283        Log.w("AndroidScrollIntegrationTest", String.format("scroll in Js (%d, %d) -> (%d, %d)",
284                    targetScrollXCss, targetScrollYCss, targetScrollXPix, targetScrollYPix));
285
286        getInstrumentation().runOnMainSync(new Runnable() {
287            @Override
288            public void run() {
289                onscrollObserver.register(testContainerView.getContentViewCore(),
290                    "onscrollObserver");
291            }
292        });
293
294        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, "onscrollObserver", "");
295
296        scrollToOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);
297
298        onscrollObserver.waitForEvent(SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS);
299        assertScrollInJs(testContainerView.getAwContents(), contentsClient,
300                targetScrollXCss, targetScrollYCss);
301    }
302
303    @SmallTest
304    @Feature({"AndroidWebView"})
305    public void testJsScrollReflectedInUi() throws Throwable {
306        final TestAwContentsClient contentsClient = new TestAwContentsClient();
307        final ScrollTestContainerView testContainerView =
308            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
309        enableJavaScriptOnUiThread(testContainerView.getAwContents());
310
311        final double deviceDIPScale =
312            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
313        final int targetScrollXCss = 132;
314        final int targetScrollYCss = 243;
315        final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
316        final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);
317
318        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
319                makeTestPage(null, null, ""), "text/html", false);
320
321        final CallbackHelper onScrollToCallbackHelper =
322            testContainerView.getOnScrollToCallbackHelper();
323        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
324        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
325                String.format("window.scrollTo(%d, %d);", targetScrollXCss, targetScrollYCss));
326        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
327
328        assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);
329    }
330
331    @SmallTest
332    @Feature({"AndroidWebView"})
333    public void testJsScrollCanBeAlteredByUi() throws Throwable {
334        final TestAwContentsClient contentsClient = new TestAwContentsClient();
335        final ScrollTestContainerView testContainerView =
336            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
337        enableJavaScriptOnUiThread(testContainerView.getAwContents());
338
339        final double deviceDIPScale =
340            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
341        final int targetScrollXCss = 132;
342        final int targetScrollYCss = 243;
343        final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
344        final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);
345
346        final int maxScrollXCss = 101;
347        final int maxScrollYCss = 201;
348        final int maxScrollXPix = (int) Math.floor(maxScrollXCss * deviceDIPScale);
349        final int maxScrollYPix = (int) Math.floor(maxScrollYCss * deviceDIPScale);
350
351        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
352                makeTestPage(null, null, ""), "text/html", false);
353
354        setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
355
356        final CallbackHelper onScrollToCallbackHelper =
357            testContainerView.getOnScrollToCallbackHelper();
358        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
359        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
360                "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")");
361        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
362
363        assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
364    }
365
366    @SmallTest
367    @Feature({"AndroidWebView"})
368    public void testTouchScrollCanBeAlteredByUi() throws Throwable {
369        final TestAwContentsClient contentsClient = new TestAwContentsClient();
370        final ScrollTestContainerView testContainerView =
371            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
372        enableJavaScriptOnUiThread(testContainerView.getAwContents());
373
374        final int dragSteps = 10;
375        final int dragStepSize = 24;
376        // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal
377        // scroll snapping will kick in.
378        final int targetScrollXPix = dragStepSize * dragSteps;
379        final int targetScrollYPix = dragStepSize * dragSteps;
380
381        final double deviceDIPScale =
382            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
383        final int maxScrollXPix = 101;
384        final int maxScrollYPix = 211;
385        // Make sure we can't hit these values simply as a result of scrolling.
386        assert (maxScrollXPix % dragStepSize) != 0;
387        assert (maxScrollYPix % dragStepSize) != 0;
388        final int maxScrollXCss = (int) Math.floor(maxScrollXPix / deviceDIPScale);
389        final int maxScrollYCss = (int) Math.floor(maxScrollYPix / deviceDIPScale);
390
391        setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
392
393        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
394
395        final CallbackHelper onScrollToCallbackHelper =
396            testContainerView.getOnScrollToCallbackHelper();
397        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
398        AwTestTouchUtils.dragCompleteView(testContainerView,
399                0, -targetScrollXPix, // these need to be negative as we're scrolling down.
400                0, -targetScrollYPix,
401                dragSteps,
402                null /* completionLatch */);
403
404        for (int i = 1; i <= dragSteps; ++i) {
405            onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
406            if (checkScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix))
407                break;
408        }
409
410        assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
411        assertScrollInJs(testContainerView.getAwContents(), contentsClient,
412                maxScrollXCss, maxScrollYCss);
413    }
414
415    @SmallTest
416    @Feature({"AndroidWebView"})
417    public void testNoSpuriousOverScrolls() throws Throwable {
418        final TestAwContentsClient contentsClient = new TestAwContentsClient();
419        final ScrollTestContainerView testContainerView =
420            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
421        enableJavaScriptOnUiThread(testContainerView.getAwContents());
422
423        final int dragSteps = 1;
424        final int targetScrollYPix = 24;
425
426        setMaxScrollOnMainSync(testContainerView, 0, 0);
427
428        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
429
430        final CallbackHelper onScrollToCallbackHelper =
431            testContainerView.getOnScrollToCallbackHelper();
432        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
433        CountDownLatch scrollingCompleteLatch = new CountDownLatch(1);
434        AwTestTouchUtils.dragCompleteView(testContainerView,
435                0, 0, // these need to be negative as we're scrolling down.
436                0, -targetScrollYPix,
437                dragSteps,
438                scrollingCompleteLatch);
439        try {
440            scrollingCompleteLatch.await();
441        } catch (InterruptedException ex) {
442            // ignore
443        }
444        assertEquals(scrollToCallCount + 1, onScrollToCallbackHelper.getCallCount());
445    }
446
447    @SmallTest
448    @Feature({"AndroidWebView"})
449    public void testOverScrollX() throws Throwable {
450        final TestAwContentsClient contentsClient = new TestAwContentsClient();
451        final ScrollTestContainerView testContainerView =
452            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
453        final OverScrollByCallbackHelper overScrollByCallbackHelper =
454            testContainerView.getOverScrollByCallbackHelper();
455        enableJavaScriptOnUiThread(testContainerView.getAwContents());
456
457        final int overScrollDeltaX = 30;
458        final int oneStep = 1;
459
460        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
461
462        // Scroll separately in different dimensions because of vertical/horizontal scroll
463        // snap.
464        final int overScrollCallCount = overScrollByCallbackHelper.getCallCount();
465        AwTestTouchUtils.dragCompleteView(testContainerView,
466                0, overScrollDeltaX,
467                0, 0,
468                oneStep,
469                null /* completionLatch */);
470        overScrollByCallbackHelper.waitForCallback(overScrollCallCount);
471        // Unfortunately the gesture detector seems to 'eat' some number of pixels. For now
472        // checking that the value is < 0 (overscroll is reported as negative values) will have to
473        // do.
474        assertTrue(0 > overScrollByCallbackHelper.getDeltaX());
475        assertEquals(0, overScrollByCallbackHelper.getDeltaY());
476
477        assertScrollOnMainSync(testContainerView, 0, 0);
478    }
479
480    @SmallTest
481    @Feature({"AndroidWebView"})
482    public void testOverScrollY() throws Throwable {
483        final TestAwContentsClient contentsClient = new TestAwContentsClient();
484        final ScrollTestContainerView testContainerView =
485            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
486        final OverScrollByCallbackHelper overScrollByCallbackHelper =
487            testContainerView.getOverScrollByCallbackHelper();
488        enableJavaScriptOnUiThread(testContainerView.getAwContents());
489
490        final int overScrollDeltaY = 30;
491        final int oneStep = 1;
492
493        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
494
495        int overScrollCallCount = overScrollByCallbackHelper.getCallCount();
496        AwTestTouchUtils.dragCompleteView(testContainerView,
497                0, 0,
498                0, overScrollDeltaY,
499                oneStep,
500                null /* completionLatch */);
501        overScrollByCallbackHelper.waitForCallback(overScrollCallCount);
502        assertEquals(0, overScrollByCallbackHelper.getDeltaX());
503        assertTrue(0 > overScrollByCallbackHelper.getDeltaY());
504
505        assertScrollOnMainSync(testContainerView, 0, 0);
506    }
507
508    @SmallTest
509    @Feature({"AndroidWebView"})
510    public void testScrollToBottomAtPageScaleX0dot5() throws Throwable {
511        // The idea behind this test is to check that scrolling to the bottom on ther renderer side
512        // results in the view also reporting as being scrolled to the bottom.
513        final TestAwContentsClient contentsClient = new TestAwContentsClient();
514        final ScrollTestContainerView testContainerView =
515            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
516        enableJavaScriptOnUiThread(testContainerView.getAwContents());
517
518        final int targetScrollXCss = 1000;
519        final int targetScrollYCss = 10000;
520
521        final String pageHeaders =
522            "<meta name=\"viewport\" content=\"width=device-width, initial-scale=0.6\"> " +
523            "<style type=\"text/css\"> " +
524            "   div { " +
525            "      width:1000px; " +
526            "      height:10000px; " +
527            "      background-color: blue; " +
528            "   } " +
529            "   body { " +
530            "      margin: 0px; " +
531            "      padding: 0px; " +
532            "   } " +
533            "</style> ";
534
535        loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
536                CommonResources.makeHtmlPageFrom(pageHeaders, TEST_PAGE_COMMON_CONTENT),
537                "text/html", false);
538
539        final double deviceDIPScale =
540            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
541
542        final CallbackHelper onScrollToCallbackHelper =
543            testContainerView.getOnScrollToCallbackHelper();
544        int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
545        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
546                "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")");
547        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
548
549        getInstrumentation().runOnMainSync(new Runnable() {
550            @Override
551            public void run() {
552                AwContents awContents = testContainerView.getAwContents();
553                int maxHorizontal = awContents.computeHorizontalScrollRange() -
554                                testContainerView.getWidth();
555                int maxVertical = awContents.computeVerticalScrollRange() -
556                                testContainerView.getHeight();
557                // Due to rounding going from CSS -> physical pixels it is possible that more than
558                // one physical pixels corespond to one CSS pixel, which is why we can't do a
559                // simple equality test here.
560                assertTrue(maxHorizontal - awContents.computeHorizontalScrollOffset() < 3);
561                assertTrue(maxVertical - awContents.computeVerticalScrollOffset() < 3);
562            }
563        });
564
565        scrollToCallCount = onScrollToCallbackHelper.getCallCount();
566        executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
567                "window.scrollTo(0, 0)");
568        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
569
570        getInstrumentation().runOnMainSync(new Runnable() {
571            @Override
572            public void run() {
573                AwContents awContents = testContainerView.getAwContents();
574                int maxHorizontal = awContents.computeHorizontalScrollRange() -
575                                testContainerView.getWidth();
576                int maxVertical = awContents.computeVerticalScrollRange() -
577                                testContainerView.getHeight();
578                testContainerView.scrollTo(maxHorizontal, maxVertical);
579            }
580        });
581        assertScrolledToBottomInJs(testContainerView.getAwContents(), contentsClient);
582    }
583
584    @SmallTest
585    @Feature({"AndroidWebView"})
586    public void testFlingScroll() throws Throwable {
587        final TestAwContentsClient contentsClient = new TestAwContentsClient();
588        final ScrollTestContainerView testContainerView =
589            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
590        enableJavaScriptOnUiThread(testContainerView.getAwContents());
591
592        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
593
594        assertScrollOnMainSync(testContainerView, 0, 0);
595
596        final CallbackHelper onScrollToCallbackHelper =
597            testContainerView.getOnScrollToCallbackHelper();
598        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
599
600        getInstrumentation().runOnMainSync(new Runnable() {
601            @Override
602            public void run() {
603                testContainerView.getAwContents().flingScroll(1000, 1000);
604            }
605        });
606
607        onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
608
609        getInstrumentation().runOnMainSync(new Runnable() {
610            @Override
611            public void run() {
612                assertTrue(testContainerView.getScrollX() > 0);
613                assertTrue(testContainerView.getScrollY() > 0);
614            }
615        });
616    }
617
618    @SmallTest
619    @Feature({"AndroidWebView"})
620    public void testPageDown() throws Throwable {
621        final TestAwContentsClient contentsClient = new TestAwContentsClient();
622        final ScrollTestContainerView testContainerView =
623            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
624        enableJavaScriptOnUiThread(testContainerView.getAwContents());
625
626        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
627
628        assertScrollOnMainSync(testContainerView, 0, 0);
629
630        final int maxScrollYPix = runTestOnUiThreadAndGetResult(new Callable<Integer>() {
631            @Override
632            public Integer call() {
633                return (testContainerView.getAwContents().computeVerticalScrollRange() -
634                    testContainerView.getHeight());
635            }
636        });
637
638        final CallbackHelper onScrollToCallbackHelper =
639            testContainerView.getOnScrollToCallbackHelper();
640        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
641
642        getInstrumentation().runOnMainSync(new Runnable() {
643            @Override
644            public void run() {
645                testContainerView.getAwContents().pageDown(true);
646            }
647        });
648
649        // Wait for the animation to hit the bottom of the page.
650        for (int i = 1; ; ++i) {
651            onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
652            if (checkScrollOnMainSync(testContainerView, 0, maxScrollYPix))
653                break;
654        }
655    }
656
657    @SmallTest
658    @Feature({"AndroidWebView"})
659    public void testPageUp() throws Throwable {
660        final TestAwContentsClient contentsClient = new TestAwContentsClient();
661        final ScrollTestContainerView testContainerView =
662            (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
663        enableJavaScriptOnUiThread(testContainerView.getAwContents());
664
665        final double deviceDIPScale =
666            DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
667        final int targetScrollYCss = 243;
668        final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);
669
670        loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
671
672        assertScrollOnMainSync(testContainerView, 0, 0);
673
674        scrollToOnMainSync(testContainerView, 0, targetScrollYPix);
675
676        final CallbackHelper onScrollToCallbackHelper =
677            testContainerView.getOnScrollToCallbackHelper();
678        final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
679
680        getInstrumentation().runOnMainSync(new Runnable() {
681            @Override
682            public void run() {
683                testContainerView.getAwContents().pageUp(true);
684            }
685        });
686
687        // Wait for the animation to hit the bottom of the page.
688        for (int i = 1; ; ++i) {
689            onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
690            if (checkScrollOnMainSync(testContainerView, 0, 0))
691                break;
692        }
693    }
694}
695