• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.android_webview.test;
6 
7 import android.content.Context;
8 import android.test.suitebuilder.annotation.SmallTest;
9 import android.view.View;
10 import android.widget.OverScroller;
11 
12 import org.chromium.android_webview.AwContents;
13 import org.chromium.android_webview.AwScrollOffsetManager;
14 import org.chromium.android_webview.test.util.AwTestTouchUtils;
15 import org.chromium.android_webview.test.util.CommonResources;
16 import org.chromium.android_webview.test.util.JavascriptEventObserver;
17 import org.chromium.base.test.util.Feature;
18 import org.chromium.content.browser.test.util.CallbackHelper;
19 import org.chromium.content_public.browser.GestureStateListener;
20 import org.chromium.ui.gfx.DeviceDisplayInfo;
21 
22 import java.util.Locale;
23 import java.util.concurrent.Callable;
24 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.atomic.AtomicBoolean;
26 
27 /**
28  * Integration tests for synchronous scrolling.
29  */
30 public class AndroidScrollIntegrationTest extends AwTestBase {
31     private static class OverScrollByCallbackHelper extends CallbackHelper {
32         int mDeltaX;
33         int mDeltaY;
34         int mScrollRangeY;
35 
getDeltaX()36         public int getDeltaX() {
37             assert getCallCount() > 0;
38             return mDeltaX;
39         }
40 
getDeltaY()41         public int getDeltaY() {
42             assert getCallCount() > 0;
43             return mDeltaY;
44         }
45 
getScrollRangeY()46         public int getScrollRangeY() {
47             assert getCallCount() > 0;
48             return mScrollRangeY;
49         }
50 
notifyCalled(int deltaX, int deltaY, int scrollRangeY)51         public void notifyCalled(int deltaX, int deltaY, int scrollRangeY) {
52             mDeltaX = deltaX;
53             mDeltaY = deltaY;
54             mScrollRangeY = scrollRangeY;
55             notifyCalled();
56         }
57     }
58 
59     private static class ScrollTestContainerView extends AwTestContainerView {
60         private int mMaxScrollXPix = -1;
61         private int mMaxScrollYPix = -1;
62 
63         private CallbackHelper mOnScrollToCallbackHelper = new CallbackHelper();
64         private OverScrollByCallbackHelper mOverScrollByCallbackHelper =
65             new OverScrollByCallbackHelper();
66 
ScrollTestContainerView(Context context)67         public ScrollTestContainerView(Context context) {
68             super(context);
69         }
70 
getOnScrollToCallbackHelper()71         public CallbackHelper getOnScrollToCallbackHelper() {
72             return mOnScrollToCallbackHelper;
73         }
74 
getOverScrollByCallbackHelper()75         public OverScrollByCallbackHelper getOverScrollByCallbackHelper() {
76             return mOverScrollByCallbackHelper;
77         }
78 
setMaxScrollX(int maxScrollXPix)79         public void setMaxScrollX(int maxScrollXPix) {
80             mMaxScrollXPix = maxScrollXPix;
81         }
82 
setMaxScrollY(int maxScrollYPix)83         public void setMaxScrollY(int maxScrollYPix) {
84             mMaxScrollYPix = maxScrollYPix;
85         }
86 
87         @Override
overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent)88         protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
89                      int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY,
90                      boolean isTouchEvent) {
91             mOverScrollByCallbackHelper.notifyCalled(deltaX, deltaY, scrollRangeY);
92             return super.overScrollBy(deltaX, deltaY, scrollX, scrollY,
93                      scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
94         }
95 
96         @Override
scrollTo(int x, int y)97         public void scrollTo(int x, int y) {
98             if (mMaxScrollXPix != -1)
99                 x = Math.min(mMaxScrollXPix, x);
100             if (mMaxScrollYPix != -1)
101                 y = Math.min(mMaxScrollYPix, y);
102             super.scrollTo(x, y);
103             mOnScrollToCallbackHelper.notifyCalled();
104         }
105     }
106 
107     @Override
createTestDependencyFactory()108     protected TestDependencyFactory createTestDependencyFactory() {
109         return new TestDependencyFactory() {
110             @Override
111             public AwScrollOffsetManager createScrollOffsetManager(
112                     AwScrollOffsetManager.Delegate delegate, OverScroller overScroller) {
113                 return new AwScrollOffsetManager(delegate, overScroller) {
114                     @Override
115                     public void onUnhandledFlingStartEvent(int velocityX, int velocityY) {
116                         // Intentional no-op. The synthetic scroll gestures this test creates all
117                         // happen at the same time which triggers the fling detection logic.
118                         // NOTE: this simply disables handling the gesture, flinging the AwContents
119                         // via the flingScroll API is still possible.
120                     }
121                 };
122             }
123             @Override
124             public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) {
125                 return new ScrollTestContainerView(activity);
126             }
127         };
128     }
129 
130     private static final String TEST_PAGE_COMMON_HEADERS =
131         "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> " +
132         "<style type=\"text/css\"> " +
133         "   body { " +
134         "      margin: 0px; " +
135         "   } " +
136         "   div { " +
137         "      width:1000px; " +
138         "      height:10000px; " +
139         "      background-color: blue; " +
140         "   } " +
141         "</style> ";
142     private static final String TEST_PAGE_COMMON_CONTENT = "<div>test div</div> ";
143 
144     private String makeTestPage(String onscrollObserver, String firstFrameObserver,
145             String extraContent) {
146         String content = TEST_PAGE_COMMON_CONTENT + extraContent;
147         if (onscrollObserver != null) {
148             content +=
149                     "<script> " +
150                     "   window.onscroll = function(oEvent) { " +
151                     "       " + onscrollObserver + ".notifyJava(); " +
152                     "   } " +
153                     "</script>";
154         }
155         if (firstFrameObserver != null) {
156             content +=
157                     "<script> " +
158                     "   window.framesToIgnore = 20; " +
159                     "   window.onAnimationFrame = function(timestamp) { " +
160                     "     if (window.framesToIgnore == 0) { " +
161                     "         " + firstFrameObserver + ".notifyJava(); " +
162                     "     } else {" +
163                     "       window.framesToIgnore -= 1; " +
164                     "       window.requestAnimationFrame(window.onAnimationFrame); " +
165                     "     } " +
166                     "   }; " +
167                     "   window.requestAnimationFrame(window.onAnimationFrame); " +
168                     "</script>";
169         }
170         return CommonResources.makeHtmlPageFrom(TEST_PAGE_COMMON_HEADERS, content);
171     }
172 
173     private void scrollToOnMainSync(final View view, final int xPix, final int yPix) {
174         getInstrumentation().runOnMainSync(new Runnable() {
175             @Override
176             public void run() {
177                 view.scrollTo(xPix, yPix);
178             }
179         });
180     }
181 
182     private void setMaxScrollOnMainSync(final ScrollTestContainerView testContainerView,
183             final int maxScrollXPix, final int maxScrollYPix) {
184         getInstrumentation().runOnMainSync(new Runnable() {
185             @Override
186             public void run() {
187                 testContainerView.setMaxScrollX(maxScrollXPix);
188                 testContainerView.setMaxScrollY(maxScrollYPix);
189             }
190         });
191     }
192 
193     private boolean checkScrollOnMainSync(final ScrollTestContainerView testContainerView,
194             final int scrollXPix, final int scrollYPix) {
195         final AtomicBoolean equal = new AtomicBoolean(false);
196         getInstrumentation().runOnMainSync(new Runnable() {
197             @Override
198             public void run() {
199                 equal.set((scrollXPix == testContainerView.getScrollX()) &&
200                     (scrollYPix == testContainerView.getScrollY()));
201             }
202         });
203         return equal.get();
204     }
205 
206     private void assertScrollOnMainSync(final ScrollTestContainerView testContainerView,
207             final int scrollXPix, final int scrollYPix) {
208         getInstrumentation().runOnMainSync(new Runnable() {
209             @Override
210             public void run() {
211                 assertEquals(scrollXPix, testContainerView.getScrollX());
212                 assertEquals(scrollYPix, testContainerView.getScrollY());
213             }
214         });
215     }
216 
217     private void assertScrollInJs(final AwContents awContents,
218             final TestAwContentsClient contentsClient,
219             final int xCss, final int yCss) throws Exception {
220         poll(new Callable<Boolean>() {
221             @Override
222             public Boolean call() throws Exception {
223                 String x = executeJavaScriptAndWaitForResult(awContents, contentsClient,
224                     "window.scrollX");
225                 String y = executeJavaScriptAndWaitForResult(awContents, contentsClient,
226                     "window.scrollY");
227                 return (Integer.toString(xCss).equals(x) &&
228                     Integer.toString(yCss).equals(y));
229             }
230         });
231     }
232 
233     private void assertScrolledToBottomInJs(final AwContents awContents,
234             final TestAwContentsClient contentsClient) throws Exception {
235         final String isBottomScript = "window.scrollY == " +
236             "(window.document.documentElement.scrollHeight - window.innerHeight)";
237         poll(new Callable<Boolean>() {
238             @Override
239             public Boolean call() throws Exception {
240                 String r = executeJavaScriptAndWaitForResult(awContents, contentsClient,
241                     isBottomScript);
242                 return r.equals("true");
243             }
244         });
245     }
246 
247     private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView,
248             final TestAwContentsClient contentsClient,
249             final String onscrollObserverName, final String extraContent) throws Exception {
250         final JavascriptEventObserver firstFrameObserver = new JavascriptEventObserver();
251         final String firstFrameObserverName = "firstFrameObserver";
252         enableJavaScriptOnUiThread(testContainerView.getAwContents());
253 
254         getInstrumentation().runOnMainSync(new Runnable() {
255             @Override
256             public void run() {
257                 firstFrameObserver.register(testContainerView.getContentViewCore(),
258                     firstFrameObserverName);
259             }
260         });
261 
262         loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
263                 makeTestPage(onscrollObserverName, firstFrameObserverName, extraContent),
264                 "text/html", false);
265 
266         // We wait for "a couple" of frames for the active tree in CC to stabilize and for pending
267         // tree activations to stop clobbering the root scroll layer's scroll offset. This wait
268         // doesn't strictly guarantee that but there isn't a good alternative and this seems to
269         // work fine.
270         firstFrameObserver.waitForEvent(WAIT_TIMEOUT_MS);
271     }
272 
273     @SmallTest
274     @Feature({"AndroidWebView"})
275     public void testUiScrollReflectedInJs() throws Throwable {
276         final TestAwContentsClient contentsClient = new TestAwContentsClient();
277         final ScrollTestContainerView testContainerView =
278             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
279         enableJavaScriptOnUiThread(testContainerView.getAwContents());
280 
281         final double deviceDIPScale =
282             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
283         final int targetScrollXCss = 233;
284         final int targetScrollYCss = 322;
285         final int targetScrollXPix = (int) Math.ceil(targetScrollXCss * deviceDIPScale);
286         final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);
287         final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver();
288 
289         getInstrumentation().runOnMainSync(new Runnable() {
290             @Override
291             public void run() {
292                 onscrollObserver.register(testContainerView.getContentViewCore(),
293                     "onscrollObserver");
294             }
295         });
296 
297         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, "onscrollObserver", "");
298 
299         scrollToOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);
300 
301         onscrollObserver.waitForEvent(WAIT_TIMEOUT_MS);
302         assertScrollInJs(testContainerView.getAwContents(), contentsClient,
303                 targetScrollXCss, targetScrollYCss);
304     }
305 
306     @SmallTest
307     @Feature({"AndroidWebView"})
308     public void testJsScrollReflectedInUi() throws Throwable {
309         final TestAwContentsClient contentsClient = new TestAwContentsClient();
310         final ScrollTestContainerView testContainerView =
311             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
312         enableJavaScriptOnUiThread(testContainerView.getAwContents());
313 
314         final double deviceDIPScale =
315             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
316         final int targetScrollXCss = 132;
317         final int targetScrollYCss = 243;
318         final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
319         final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);
320 
321         loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
322                 makeTestPage(null, null, ""), "text/html", false);
323 
324         final CallbackHelper onScrollToCallbackHelper =
325             testContainerView.getOnScrollToCallbackHelper();
326         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
327         executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
328                 String.format("window.scrollTo(%d, %d);", targetScrollXCss, targetScrollYCss));
329         onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
330 
331         assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);
332     }
333 
334     @SmallTest
335     @Feature({"AndroidWebView"})
336     public void testJsScrollFromBody() throws Throwable {
337         final TestAwContentsClient contentsClient = new TestAwContentsClient();
338         final ScrollTestContainerView testContainerView =
339             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
340         enableJavaScriptOnUiThread(testContainerView.getAwContents());
341 
342         final double deviceDIPScale =
343             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
344         final int targetScrollXCss = 132;
345         final int targetScrollYCss = 243;
346         final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
347         final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);
348 
349         final String scrollFromBodyScript =
350             "<script> " +
351             "  window.scrollTo(" + targetScrollXCss + ", " + targetScrollYCss + "); " +
352             "</script> ";
353 
354         final CallbackHelper onScrollToCallbackHelper =
355             testContainerView.getOnScrollToCallbackHelper();
356         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
357         loadDataAsync(testContainerView.getAwContents(),
358                 makeTestPage(null, null, scrollFromBodyScript), "text/html", false);
359         onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
360 
361         assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix);
362     }
363 
364     @SmallTest
365     @Feature({"AndroidWebView"})
366     public void testJsScrollCanBeAlteredByUi() throws Throwable {
367         final TestAwContentsClient contentsClient = new TestAwContentsClient();
368         final ScrollTestContainerView testContainerView =
369             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
370         enableJavaScriptOnUiThread(testContainerView.getAwContents());
371 
372         final double deviceDIPScale =
373             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
374         final int targetScrollXCss = 132;
375         final int targetScrollYCss = 243;
376         final int targetScrollXPix = (int) Math.floor(targetScrollXCss * deviceDIPScale);
377         final int targetScrollYPix = (int) Math.floor(targetScrollYCss * deviceDIPScale);
378 
379         final int maxScrollXCss = 101;
380         final int maxScrollYCss = 201;
381         final int maxScrollXPix = (int) Math.floor(maxScrollXCss * deviceDIPScale);
382         final int maxScrollYPix = (int) Math.floor(maxScrollYCss * deviceDIPScale);
383 
384         loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
385                 makeTestPage(null, null, ""), "text/html", false);
386 
387         setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
388 
389         final CallbackHelper onScrollToCallbackHelper =
390             testContainerView.getOnScrollToCallbackHelper();
391         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
392         executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
393                 "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")");
394         onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
395 
396         assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
397     }
398 
399     @SmallTest
400     @Feature({"AndroidWebView"})
401     public void testTouchScrollCanBeAlteredByUi() throws Throwable {
402         final TestAwContentsClient contentsClient = new TestAwContentsClient();
403         final ScrollTestContainerView testContainerView =
404             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
405         enableJavaScriptOnUiThread(testContainerView.getAwContents());
406 
407         final int dragSteps = 10;
408         final int dragStepSize = 24;
409         // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal
410         // scroll snapping will kick in.
411         final int targetScrollXPix = dragStepSize * dragSteps;
412         final int targetScrollYPix = dragStepSize * dragSteps;
413 
414         final double deviceDIPScale =
415             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
416         final int maxScrollXPix = 101;
417         final int maxScrollYPix = 211;
418         // Make sure we can't hit these values simply as a result of scrolling.
419         assert (maxScrollXPix % dragStepSize) != 0;
420         assert (maxScrollYPix % dragStepSize) != 0;
421         final int maxScrollXCss = (int) Math.floor(maxScrollXPix / deviceDIPScale);
422         final int maxScrollYCss = (int) Math.floor(maxScrollYPix / deviceDIPScale);
423 
424         setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
425 
426         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
427 
428         final CallbackHelper onScrollToCallbackHelper =
429             testContainerView.getOnScrollToCallbackHelper();
430         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
431         AwTestTouchUtils.dragCompleteView(testContainerView,
432                 0, -targetScrollXPix, // these need to be negative as we're scrolling down.
433                 0, -targetScrollYPix,
434                 dragSteps,
435                 null /* completionLatch */);
436 
437         for (int i = 1; i <= dragSteps; ++i) {
438             onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
439             if (checkScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix))
440                 break;
441         }
442 
443         assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix);
444         assertScrollInJs(testContainerView.getAwContents(), contentsClient,
445                 maxScrollXCss, maxScrollYCss);
446     }
447 
448     @SmallTest
449     @Feature({"AndroidWebView"})
450     public void testNoSpuriousOverScrolls() throws Throwable {
451         final TestAwContentsClient contentsClient = new TestAwContentsClient();
452         final ScrollTestContainerView testContainerView =
453             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
454         enableJavaScriptOnUiThread(testContainerView.getAwContents());
455 
456         final int dragSteps = 1;
457         final int targetScrollYPix = 40;
458 
459         setMaxScrollOnMainSync(testContainerView, 0, 0);
460 
461         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
462 
463         final CallbackHelper onScrollToCallbackHelper =
464             testContainerView.getOnScrollToCallbackHelper();
465         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
466         CountDownLatch scrollingCompleteLatch = new CountDownLatch(1);
467         AwTestTouchUtils.dragCompleteView(testContainerView,
468                 0, 0, // these need to be negative as we're scrolling down.
469                 0, -targetScrollYPix,
470                 dragSteps,
471                 scrollingCompleteLatch);
472         try {
473             scrollingCompleteLatch.await();
474         } catch (InterruptedException ex) {
475             // ignore
476         }
477         assertEquals(scrollToCallCount + 1, onScrollToCallbackHelper.getCallCount());
478     }
479 
480     @SmallTest
481     @Feature({"AndroidWebView"})
482     public void testOverScrollX() 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 overScrollDeltaX = 30;
491         final int oneStep = 1;
492 
493         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
494 
495         // Scroll separately in different dimensions because of vertical/horizontal scroll
496         // snap.
497         final int overScrollCallCount = overScrollByCallbackHelper.getCallCount();
498         AwTestTouchUtils.dragCompleteView(testContainerView,
499                 0, overScrollDeltaX,
500                 0, 0,
501                 oneStep,
502                 null /* completionLatch */);
503         overScrollByCallbackHelper.waitForCallback(overScrollCallCount);
504         // Unfortunately the gesture detector seems to 'eat' some number of pixels. For now
505         // checking that the value is < 0 (overscroll is reported as negative values) will have to
506         // do.
507         assertTrue(0 > overScrollByCallbackHelper.getDeltaX());
508         assertEquals(0, overScrollByCallbackHelper.getDeltaY());
509 
510         assertScrollOnMainSync(testContainerView, 0, 0);
511     }
512 
513     @SmallTest
514     @Feature({"AndroidWebView"})
515     public void testOverScrollY() throws Throwable {
516         final TestAwContentsClient contentsClient = new TestAwContentsClient();
517         final ScrollTestContainerView testContainerView =
518             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
519         final OverScrollByCallbackHelper overScrollByCallbackHelper =
520             testContainerView.getOverScrollByCallbackHelper();
521         enableJavaScriptOnUiThread(testContainerView.getAwContents());
522 
523         final int overScrollDeltaY = 30;
524         final int oneStep = 1;
525 
526         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
527 
528         int overScrollCallCount = overScrollByCallbackHelper.getCallCount();
529         AwTestTouchUtils.dragCompleteView(testContainerView,
530                 0, 0,
531                 0, overScrollDeltaY,
532                 oneStep,
533                 null /* completionLatch */);
534         overScrollByCallbackHelper.waitForCallback(overScrollCallCount);
535         assertEquals(0, overScrollByCallbackHelper.getDeltaX());
536         assertTrue(0 > overScrollByCallbackHelper.getDeltaY());
537 
538         assertScrollOnMainSync(testContainerView, 0, 0);
539     }
540 
541     @SmallTest
542     @Feature({"AndroidWebView"})
543     public void testScrollToBottomAtPageScaleX0dot5() throws Throwable {
544         // The idea behind this test is to check that scrolling to the bottom on ther renderer side
545         // results in the view also reporting as being scrolled to the bottom.
546         final TestAwContentsClient contentsClient = new TestAwContentsClient();
547         final ScrollTestContainerView testContainerView =
548             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
549         enableJavaScriptOnUiThread(testContainerView.getAwContents());
550 
551         final int targetScrollXCss = 1000;
552         final int targetScrollYCss = 10000;
553 
554         final String pageHeaders =
555             "<meta name=\"viewport\" content=\"width=device-width, initial-scale=0.6\"> " +
556             "<style type=\"text/css\"> " +
557             "   div { " +
558             "      width:1000px; " +
559             "      height:10000px; " +
560             "      background-color: blue; " +
561             "   } " +
562             "   body { " +
563             "      margin: 0px; " +
564             "      padding: 0px; " +
565             "   } " +
566             "</style> ";
567 
568         loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(),
569                 CommonResources.makeHtmlPageFrom(pageHeaders, TEST_PAGE_COMMON_CONTENT),
570                 "text/html", false);
571 
572         final double deviceDIPScale =
573             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
574 
575         final CallbackHelper onScrollToCallbackHelper =
576             testContainerView.getOnScrollToCallbackHelper();
577         int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
578         executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
579                 "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")");
580         onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
581 
582         getInstrumentation().runOnMainSync(new Runnable() {
583             @Override
584             public void run() {
585                 AwContents awContents = testContainerView.getAwContents();
586                 int maxHorizontal = awContents.computeHorizontalScrollRange() -
587                                 testContainerView.getWidth();
588                 int maxVertical = awContents.computeVerticalScrollRange() -
589                                 testContainerView.getHeight();
590                 // Due to rounding going from CSS -> physical pixels it is possible that more than
591                 // one physical pixels corespond to one CSS pixel, which is why we can't do a
592                 // simple equality test here.
593                 assertTrue(maxHorizontal - awContents.computeHorizontalScrollOffset() < 3);
594                 assertTrue(maxVertical - awContents.computeVerticalScrollOffset() < 3);
595             }
596         });
597 
598         scrollToCallCount = onScrollToCallbackHelper.getCallCount();
599         executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient,
600                 "window.scrollTo(0, 0)");
601         onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
602 
603         getInstrumentation().runOnMainSync(new Runnable() {
604             @Override
605             public void run() {
606                 AwContents awContents = testContainerView.getAwContents();
607                 int maxHorizontal = awContents.computeHorizontalScrollRange() -
608                                 testContainerView.getWidth();
609                 int maxVertical = awContents.computeVerticalScrollRange() -
610                                 testContainerView.getHeight();
611                 testContainerView.scrollTo(maxHorizontal, maxVertical);
612             }
613         });
614         assertScrolledToBottomInJs(testContainerView.getAwContents(), contentsClient);
615     }
616 
617     @SmallTest
618     @Feature({"AndroidWebView"})
619     public void testFlingScroll() throws Throwable {
620         final TestAwContentsClient contentsClient = new TestAwContentsClient();
621         final ScrollTestContainerView testContainerView =
622             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
623         enableJavaScriptOnUiThread(testContainerView.getAwContents());
624 
625         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
626 
627         assertScrollOnMainSync(testContainerView, 0, 0);
628 
629         final CallbackHelper onScrollToCallbackHelper =
630             testContainerView.getOnScrollToCallbackHelper();
631         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
632 
633         getInstrumentation().runOnMainSync(new Runnable() {
634             @Override
635             public void run() {
636                 testContainerView.getAwContents().flingScroll(1000, 1000);
637             }
638         });
639 
640         onScrollToCallbackHelper.waitForCallback(scrollToCallCount);
641 
642         getInstrumentation().runOnMainSync(new Runnable() {
643             @Override
644             public void run() {
645                 assertTrue(testContainerView.getScrollX() > 0);
646                 assertTrue(testContainerView.getScrollY() > 0);
647             }
648         });
649     }
650 
651     @SmallTest
652     @Feature({"AndroidWebView"})
653     public void testPageDown() throws Throwable {
654         final TestAwContentsClient contentsClient = new TestAwContentsClient();
655         final ScrollTestContainerView testContainerView =
656             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
657         enableJavaScriptOnUiThread(testContainerView.getAwContents());
658 
659         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
660 
661         assertScrollOnMainSync(testContainerView, 0, 0);
662 
663         final int maxScrollYPix = runTestOnUiThreadAndGetResult(new Callable<Integer>() {
664             @Override
665             public Integer call() {
666                 return (testContainerView.getAwContents().computeVerticalScrollRange() -
667                     testContainerView.getHeight());
668             }
669         });
670 
671         final CallbackHelper onScrollToCallbackHelper =
672             testContainerView.getOnScrollToCallbackHelper();
673         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
674 
675         getInstrumentation().runOnMainSync(new Runnable() {
676             @Override
677             public void run() {
678                 testContainerView.getAwContents().pageDown(true);
679             }
680         });
681 
682         // Wait for the animation to hit the bottom of the page.
683         for (int i = 1;; ++i) {
684             onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
685             if (checkScrollOnMainSync(testContainerView, 0, maxScrollYPix))
686                 break;
687         }
688     }
689 
690     @SmallTest
691     @Feature({"AndroidWebView"})
692     public void testPageUp() throws Throwable {
693         final TestAwContentsClient contentsClient = new TestAwContentsClient();
694         final ScrollTestContainerView testContainerView =
695             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
696         enableJavaScriptOnUiThread(testContainerView.getAwContents());
697 
698         final double deviceDIPScale =
699             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
700         final int targetScrollYCss = 243;
701         final int targetScrollYPix = (int) Math.ceil(targetScrollYCss * deviceDIPScale);
702 
703         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
704 
705         assertScrollOnMainSync(testContainerView, 0, 0);
706 
707         scrollToOnMainSync(testContainerView, 0, targetScrollYPix);
708 
709         final CallbackHelper onScrollToCallbackHelper =
710             testContainerView.getOnScrollToCallbackHelper();
711         final int scrollToCallCount = onScrollToCallbackHelper.getCallCount();
712 
713         getInstrumentation().runOnMainSync(new Runnable() {
714             @Override
715             public void run() {
716                 testContainerView.getAwContents().pageUp(true);
717             }
718         });
719 
720         // Wait for the animation to hit the bottom of the page.
721         for (int i = 1;; ++i) {
722             onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i);
723             if (checkScrollOnMainSync(testContainerView, 0, 0))
724                 break;
725         }
726     }
727 
728     private static class TestGestureStateListener extends GestureStateListener {
729         private CallbackHelper mOnScrollUpdateGestureConsumedHelper = new CallbackHelper();
730 
731         public CallbackHelper getOnScrollUpdateGestureConsumedHelper() {
732             return mOnScrollUpdateGestureConsumedHelper;
733         }
734 
735         @Override
736         public void onPinchStarted() {
737         }
738 
739         @Override
740         public void onPinchEnded() {
741         }
742 
743         @Override
744         public void onFlingStartGesture(
745                 int velocityX, int velocityY, int scrollOffsetY, int scrollExtentY) {
746         }
747 
748         @Override
749         public void onFlingCancelGesture() {
750         }
751 
752         @Override
753         public void onUnhandledFlingStartEvent(int velocityX, int velocityY) {
754         }
755 
756         @Override
757         public void onScrollUpdateGestureConsumed() {
758             mOnScrollUpdateGestureConsumedHelper.notifyCalled();
759         }
760     }
761 
762     @SmallTest
763     @Feature({"AndroidWebView"})
764     public void testTouchScrollingConsumesScrollByGesture() throws Throwable {
765         final TestAwContentsClient contentsClient = new TestAwContentsClient();
766         final ScrollTestContainerView testContainerView =
767             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
768         final TestGestureStateListener testGestureStateListener = new TestGestureStateListener();
769         enableJavaScriptOnUiThread(testContainerView.getAwContents());
770 
771         final int dragSteps = 10;
772         final int dragStepSize = 24;
773         // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal
774         // scroll snapping will kick in.
775         final int targetScrollXPix = dragStepSize * dragSteps;
776         final int targetScrollYPix = dragStepSize * dragSteps;
777 
778         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null,
779                 "<div>" +
780                 "  <div style=\"width:10000px; height: 10000px;\"> force scrolling </div>" +
781                 "</div>");
782 
783         getInstrumentation().runOnMainSync(new Runnable() {
784             @Override
785             public void run() {
786                 testContainerView.getContentViewCore().addGestureStateListener(
787                         testGestureStateListener);
788             }
789         });
790         final CallbackHelper onScrollUpdateGestureConsumedHelper =
791             testGestureStateListener.getOnScrollUpdateGestureConsumedHelper();
792 
793         final int callCount = onScrollUpdateGestureConsumedHelper.getCallCount();
794         AwTestTouchUtils.dragCompleteView(testContainerView,
795                 0, -targetScrollXPix, // these need to be negative as we're scrolling down.
796                 0, -targetScrollYPix,
797                 dragSteps,
798                 null /* completionLatch */);
799         onScrollUpdateGestureConsumedHelper.waitForCallback(callCount);
800     }
801 
802     @SmallTest
803     @Feature({"AndroidWebView"})
804     public void testPinchZoomUpdatesScrollRangeSynchronously() throws Throwable {
805         final TestAwContentsClient contentsClient = new TestAwContentsClient();
806         final ScrollTestContainerView testContainerView =
807             (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient);
808         final OverScrollByCallbackHelper overScrollByCallbackHelper =
809             testContainerView.getOverScrollByCallbackHelper();
810         final AwContents awContents = testContainerView.getAwContents();
811         enableJavaScriptOnUiThread(awContents);
812 
813         loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, "");
814 
815         getInstrumentation().runOnMainSync(new Runnable() {
816             @Override
817             public void run() {
818                 assertTrue(awContents.canZoomIn());
819 
820                 int oldScrollRange =
821                     awContents.computeVerticalScrollRange() - testContainerView.getHeight();
822                 float oldScale = awContents.getScale();
823                 int oldContentHeightApproximation =
824                     (int) Math.ceil(awContents.computeVerticalScrollRange() / oldScale);
825 
826                 awContents.zoomIn();
827 
828                 int newScrollRange =
829                     awContents.computeVerticalScrollRange() - testContainerView.getHeight();
830                 float newScale = awContents.getScale();
831                 int newContentHeightApproximation =
832                     (int) Math.ceil(awContents.computeVerticalScrollRange() / newScale);
833 
834                 assertTrue(String.format(Locale.ENGLISH,
835                         "Scale range should increase after zoom (%f) > (%f)",
836                         newScale, oldScale), newScale > oldScale);
837                 assertTrue(String.format(Locale.ENGLISH,
838                         "Scroll range should increase after zoom (%d) > (%d)",
839                         newScrollRange, oldScrollRange), newScrollRange > oldScrollRange);
840                 assertEquals(awContents.getContentHeightCss(), oldContentHeightApproximation);
841                 assertEquals(awContents.getContentHeightCss(), newContentHeightApproximation);
842             }
843         });
844 
845     }
846 }
847