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