AndroidScrollIntegrationTest.java revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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.CommonResources; 16import org.chromium.android_webview.test.util.JavascriptEventObserver; 17import org.chromium.base.test.util.DisabledTest; 18import org.chromium.base.test.util.Feature; 19import org.chromium.content.browser.ContentViewCore; 20import org.chromium.content.browser.test.util.CallbackHelper; 21import org.chromium.content.browser.test.util.CallbackHelper; 22import org.chromium.content.browser.test.util.TestTouchUtils; 23import org.chromium.ui.gfx.DeviceDisplayInfo; 24 25import java.util.concurrent.atomic.AtomicBoolean; 26 27/** 28 * Integration tests for synchronous scrolling. 29 */ 30public class AndroidScrollIntegrationTest extends AwTestBase { 31 32 public static final int SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS = 6 * 1000; 33 34 private static class ScrollTestContainerView extends AwTestContainerView { 35 private int mMaxScrollXPix = -1; 36 private int mMaxScrollYPix = -1; 37 38 private CallbackHelper mOnScrollToCallbackHelper = new CallbackHelper(); 39 40 public ScrollTestContainerView(Context context) { 41 super(context); 42 } 43 44 public CallbackHelper getOnScrollToCallbackHelper() { 45 return mOnScrollToCallbackHelper; 46 } 47 48 public void setMaxScrollX(int maxScrollXPix) { 49 mMaxScrollXPix = maxScrollXPix; 50 } 51 52 public void setMaxScrollY(int maxScrollYPix) { 53 mMaxScrollYPix = maxScrollYPix; 54 } 55 56 @Override 57 public void scrollTo(int x, int y) { 58 if (mMaxScrollXPix != -1) 59 x = Math.min(mMaxScrollXPix, x); 60 if (mMaxScrollYPix != -1) 61 y = Math.min(mMaxScrollYPix, y); 62 super.scrollTo(x, y); 63 mOnScrollToCallbackHelper.notifyCalled(); 64 } 65 } 66 67 @Override 68 protected TestDependencyFactory createTestDependencyFactory() { 69 return new TestDependencyFactory() { 70 @Override 71 public AwTestContainerView createAwTestContainerView(AwTestRunnerActivity activity) { 72 return new ScrollTestContainerView(activity); 73 } 74 }; 75 } 76 77 private String makeTestPage(String onscrollObserver, String firstFrameObserver) { 78 String headers = 79 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"> " + 80 "<style type=\"text/css\"> " + 81 " div { " + 82 " width:1000px; " + 83 " height:10000px; " + 84 " background-color: blue; " + 85 " } " + 86 "</style> "; 87 String content = "<div>test div</div> "; 88 if (onscrollObserver != null) { 89 content += 90 "<script> " + 91 " window.onscroll = function(oEvent) { " + 92 " " + onscrollObserver + ".notifyJava(); " + 93 " } " + 94 "</script>"; 95 } 96 if (firstFrameObserver != null) { 97 content += 98 "<script> " + 99 " window.framesToIgnore = 10; " + 100 " window.onAnimationFrame = function(timestamp) { " + 101 " if (window.framesToIgnore == 0) { " + 102 " " + firstFrameObserver + ".notifyJava(); " + 103 " } else {" + 104 " window.framesToIgnore -= 1; " + 105 " window.requestAnimationFrame(window.onAnimationFrame); " + 106 " } " + 107 " }; " + 108 " window.requestAnimationFrame(window.onAnimationFrame); " + 109 "</script>"; 110 } 111 return CommonResources.makeHtmlPageFrom(headers, content); 112 } 113 114 private void scrollToOnMainSync(final View view, final int xPix, final int yPix) { 115 getInstrumentation().runOnMainSync(new Runnable() { 116 @Override 117 public void run() { 118 view.scrollTo(xPix, yPix); 119 } 120 }); 121 } 122 123 private void setMaxScrollOnMainSync(final ScrollTestContainerView testContainerView, 124 final int maxScrollXPix, final int maxScrollYPix) { 125 getInstrumentation().runOnMainSync(new Runnable() { 126 @Override 127 public void run() { 128 testContainerView.setMaxScrollX(maxScrollXPix); 129 testContainerView.setMaxScrollY(maxScrollYPix); 130 } 131 }); 132 } 133 134 private boolean checkScrollOnMainSync(final ScrollTestContainerView testContainerView, 135 final int scrollXPix, final int scrollYPix) { 136 final AtomicBoolean equal = new AtomicBoolean(false); 137 getInstrumentation().runOnMainSync(new Runnable() { 138 @Override 139 public void run() { 140 equal.set((scrollXPix == testContainerView.getScrollX()) && 141 (scrollYPix == testContainerView.getScrollY())); 142 } 143 }); 144 return equal.get(); 145 } 146 147 private void assertScrollOnMainSync(final ScrollTestContainerView testContainerView, 148 final int scrollXPix, final int scrollYPix) { 149 getInstrumentation().runOnMainSync(new Runnable() { 150 @Override 151 public void run() { 152 assertEquals(scrollXPix, testContainerView.getScrollX()); 153 assertEquals(scrollYPix, testContainerView.getScrollY()); 154 } 155 }); 156 } 157 158 private void assertScrollInJs(AwContents awContents, TestAwContentsClient contentsClient, 159 int xCss, int yCss) throws Exception { 160 String x = executeJavaScriptAndWaitForResult(awContents, contentsClient, "window.scrollX"); 161 String y = executeJavaScriptAndWaitForResult(awContents, contentsClient, "window.scrollY"); 162 assertEquals(Integer.toString(xCss), x); 163 assertEquals(Integer.toString(yCss), y); 164 } 165 166 private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView, 167 final TestAwContentsClient contentsClient, 168 final String onscrollObserverName) throws Exception { 169 final JavascriptEventObserver firstFrameObserver = new JavascriptEventObserver(); 170 final String firstFrameObserverName = "firstFrameObserver"; 171 enableJavaScriptOnUiThread(testContainerView.getAwContents()); 172 173 getInstrumentation().runOnMainSync(new Runnable() { 174 @Override 175 public void run() { 176 firstFrameObserver.register(testContainerView.getContentViewCore(), 177 firstFrameObserverName); 178 } 179 }); 180 181 // After page load the view's scroll offset will be reset back to (0, 0) at least once. We 182 // set the initial scroll offset to something different so we can observe that. 183 scrollToOnMainSync(testContainerView, 10, 10); 184 185 loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), 186 makeTestPage(onscrollObserverName, firstFrameObserverName), "text/html", false); 187 188 // We wait for "a couple" of frames for the active tree in CC to stabilize and for pending 189 // tree activations to stop clobbering the root scroll layer's scroll offset. This wait 190 // doesn't strictly guarantee that but there isn't a good alternative and this seems to 191 // work fine. 192 firstFrameObserver.waitForEvent(WAIT_TIMEOUT_SECONDS * 1000); 193 // This is just to double-check that the animation frame waiting code didn't exit too soon. 194 assertScrollOnMainSync(testContainerView, 0, 0); 195 } 196 197 @SmallTest 198 @Feature({"AndroidWebView"}) 199 public void testUiScrollReflectedInJs() throws Throwable { 200 final TestAwContentsClient contentsClient = new TestAwContentsClient(); 201 final ScrollTestContainerView testContainerView = 202 (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); 203 enableJavaScriptOnUiThread(testContainerView.getAwContents()); 204 205 final double deviceDIPScale = 206 DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); 207 final int targetScrollXCss = 233; 208 final int targetScrollYCss = 322; 209 final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); 210 final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); 211 final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver(); 212 213 Log.w("AndroidScrollIntegrationTest", String.format("scroll in Js (%d, %d) -> (%d, %d)", 214 targetScrollXCss, targetScrollYCss, targetScrollXPix, targetScrollYPix)); 215 216 getInstrumentation().runOnMainSync(new Runnable() { 217 @Override 218 public void run() { 219 onscrollObserver.register(testContainerView.getContentViewCore(), 220 "onscrollObserver"); 221 } 222 }); 223 224 loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, "onscrollObserver"); 225 226 scrollToOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix); 227 228 onscrollObserver.waitForEvent(SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS); 229 assertScrollInJs(testContainerView.getAwContents(), contentsClient, 230 targetScrollXCss, targetScrollYCss); 231 } 232 233 @SmallTest 234 @Feature({"AndroidWebView"}) 235 public void testJsScrollReflectedInUi() throws Throwable { 236 final TestAwContentsClient contentsClient = new TestAwContentsClient(); 237 final ScrollTestContainerView testContainerView = 238 (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); 239 enableJavaScriptOnUiThread(testContainerView.getAwContents()); 240 241 final double deviceDIPScale = 242 DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); 243 final int targetScrollXCss = 132; 244 final int targetScrollYCss = 243; 245 final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); 246 final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); 247 248 loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), 249 makeTestPage(null, null), "text/html", false); 250 251 final CallbackHelper onScrollToCallbackHelper = 252 testContainerView.getOnScrollToCallbackHelper(); 253 final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); 254 executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, 255 String.format("window.scrollTo(%d, %d);", targetScrollXCss, targetScrollYCss)); 256 onScrollToCallbackHelper.waitForCallback(scrollToCallCount); 257 258 assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix); 259 } 260 261 @SmallTest 262 @Feature({"AndroidWebView"}) 263 public void testJsScrollCanBeAlteredByUi() throws Throwable { 264 final TestAwContentsClient contentsClient = new TestAwContentsClient(); 265 final ScrollTestContainerView testContainerView = 266 (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); 267 enableJavaScriptOnUiThread(testContainerView.getAwContents()); 268 269 final double deviceDIPScale = 270 DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); 271 final int targetScrollXCss = 132; 272 final int targetScrollYCss = 243; 273 final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); 274 final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); 275 276 final int maxScrollXCss = 101; 277 final int maxScrollYCss = 201; 278 final int maxScrollXPix = (int) Math.round(maxScrollXCss * deviceDIPScale); 279 final int maxScrollYPix = (int) Math.round(maxScrollYCss * deviceDIPScale); 280 281 loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), 282 makeTestPage(null, null), "text/html", false); 283 284 setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); 285 286 final CallbackHelper onScrollToCallbackHelper = 287 testContainerView.getOnScrollToCallbackHelper(); 288 final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); 289 executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, 290 "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")"); 291 onScrollToCallbackHelper.waitForCallback(scrollToCallCount); 292 293 assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); 294 } 295 296 private void dragViewBy(AwTestContainerView testContainerView, int dxPix, int dyPix, 297 int steps) { 298 int gestureStart[] = new int[2]; 299 testContainerView.getLocationOnScreen(gestureStart); 300 int gestureEnd[] = new int[] { gestureStart[0] - dxPix, gestureStart[1] - dyPix }; 301 302 TestTouchUtils.drag(this, gestureStart[0], gestureEnd[0], gestureStart[1], gestureEnd[1], 303 steps); 304 } 305 306 /* 307 * http://crbug.com/256774 308 * @SmallTest 309 * @Feature({"AndroidWebView"}) 310 */ 311 @DisabledTest 312 public void testTouchScrollCanBeAlteredByUi() throws Throwable { 313 final TestAwContentsClient contentsClient = new TestAwContentsClient(); 314 final ScrollTestContainerView testContainerView = 315 (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); 316 enableJavaScriptOnUiThread(testContainerView.getAwContents()); 317 318 final int dragSteps = 10; 319 final int dragStepSize = 24; 320 // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal 321 // scroll snapping will kick in. 322 final int targetScrollXPix = dragStepSize * dragSteps; 323 final int targetScrollYPix = dragStepSize * dragSteps; 324 325 final double deviceDIPScale = 326 DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); 327 final int maxScrollXPix = 101; 328 final int maxScrollYPix = 211; 329 // Make sure we can't hit these values simply as a result of scrolling. 330 assert (maxScrollXPix % dragStepSize) != 0; 331 assert (maxScrollYPix % dragStepSize) != 0; 332 final int maxScrollXCss = (int) Math.round(maxScrollXPix / deviceDIPScale); 333 final int maxScrollYCss = (int) Math.round(maxScrollYPix / deviceDIPScale); 334 335 setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); 336 337 loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null); 338 339 final CallbackHelper onScrollToCallbackHelper = 340 testContainerView.getOnScrollToCallbackHelper(); 341 final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); 342 dragViewBy(testContainerView, targetScrollXPix, targetScrollYPix, dragSteps); 343 344 for (int i = 1; i <= dragSteps; ++i) { 345 onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i); 346 if (checkScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix)) 347 break; 348 } 349 350 assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); 351 assertScrollInJs(testContainerView.getAwContents(), contentsClient, 352 maxScrollXCss, maxScrollYCss); 353 } 354} 355