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