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.view.View; 10import android.widget.OverScroller; 11 12import org.chromium.android_webview.AwContents; 13import org.chromium.android_webview.AwScrollOffsetManager; 14import org.chromium.android_webview.test.util.AwTestTouchUtils; 15import org.chromium.android_webview.test.util.CommonResources; 16import org.chromium.android_webview.test.util.JavascriptEventObserver; 17import org.chromium.base.test.util.Feature; 18import org.chromium.content.browser.test.util.CallbackHelper; 19import org.chromium.content_public.browser.GestureStateListener; 20import org.chromium.ui.gfx.DeviceDisplayInfo; 21 22import java.util.Locale; 23import java.util.concurrent.Callable; 24import java.util.concurrent.CountDownLatch; 25import java.util.concurrent.atomic.AtomicBoolean; 26 27/** 28 * Integration tests for synchronous scrolling. 29 */ 30public class AndroidScrollIntegrationTest extends AwTestBase { 31 private static class OverScrollByCallbackHelper extends CallbackHelper { 32 int mDeltaX; 33 int mDeltaY; 34 int mScrollRangeY; 35 36 public int getDeltaX() { 37 assert getCallCount() > 0; 38 return mDeltaX; 39 } 40 41 public int getDeltaY() { 42 assert getCallCount() > 0; 43 return mDeltaY; 44 } 45 46 public int getScrollRangeY() { 47 assert getCallCount() > 0; 48 return mScrollRangeY; 49 } 50 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 67 public ScrollTestContainerView(Context context) { 68 super(context, false); 69 } 70 71 public CallbackHelper getOnScrollToCallbackHelper() { 72 return mOnScrollToCallbackHelper; 73 } 74 75 public OverScrollByCallbackHelper getOverScrollByCallbackHelper() { 76 return mOverScrollByCallbackHelper; 77 } 78 79 public void setMaxScrollX(int maxScrollXPix) { 80 mMaxScrollXPix = maxScrollXPix; 81 } 82 83 public void setMaxScrollY(int maxScrollYPix) { 84 mMaxScrollYPix = maxScrollYPix; 85 } 86 87 @Override 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 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 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