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.util.Log; |
10 | import android.view.Gravity; |
11 | import android.view.View; |
12 | |
13 | import org.chromium.android_webview.AwContents; |
14 | import org.chromium.android_webview.AwContentsClient; |
15 | import org.chromium.android_webview.test.util.AwTestTouchUtils; |
16 | import org.chromium.android_webview.test.util.CommonResources; |
17 | import org.chromium.android_webview.test.util.JavascriptEventObserver; |
18 | import org.chromium.base.test.util.DisabledTest; |
19 | import org.chromium.base.test.util.Feature; |
20 | import org.chromium.content.browser.ContentViewCore; |
21 | import org.chromium.content.browser.test.util.CallbackHelper; |
22 | import org.chromium.content.browser.test.util.Criteria; |
23 | import org.chromium.content.browser.test.util.CriteriaHelper; |
24 | import org.chromium.ui.gfx.DeviceDisplayInfo; |
25 | |
26 | import java.util.concurrent.Callable; |
27 | import java.util.concurrent.CountDownLatch; |
28 | import java.util.concurrent.atomic.AtomicBoolean; |
29 | |
30 | /** |
31 | * Integration tests for synchronous scrolling. |
32 | */ |
33 | public 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 | String x = executeJavaScriptAndWaitForResult(awContents, contentsClient, "window.scrollX"); |
203 | String y = executeJavaScriptAndWaitForResult(awContents, contentsClient, "window.scrollY"); |
204 | |
205 | assertTrue(CriteriaHelper.pollForCriteria(new Criteria() { |
206 | @Override |
207 | public boolean isSatisfied() { |
208 | try { |
209 | String x = executeJavaScriptAndWaitForResult(awContents, contentsClient, |
210 | "window.scrollX"); |
211 | String y = executeJavaScriptAndWaitForResult(awContents, contentsClient, |
212 | "window.scrollY"); |
213 | return (Integer.toString(xCss).equals(x) && |
214 | Integer.toString(yCss).equals(y)); |
215 | } catch (Throwable t) { |
216 | t.printStackTrace(); |
217 | fail("Failed to get window.scroll(X/Y): " + t.toString()); |
218 | return false; |
219 | } |
220 | } |
221 | }, WAIT_TIMEOUT_SECONDS * 1000, CHECK_INTERVAL)); |
222 | } |
223 | |
224 | private void loadTestPageAndWaitForFirstFrame(final ScrollTestContainerView testContainerView, |
225 | final TestAwContentsClient contentsClient, |
226 | final String onscrollObserverName, final String extraContent) throws Exception { |
227 | final JavascriptEventObserver firstFrameObserver = new JavascriptEventObserver(); |
228 | final String firstFrameObserverName = "firstFrameObserver"; |
229 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
230 | |
231 | getInstrumentation().runOnMainSync(new Runnable() { |
232 | @Override |
233 | public void run() { |
234 | firstFrameObserver.register(testContainerView.getContentViewCore(), |
235 | firstFrameObserverName); |
236 | } |
237 | }); |
238 | |
239 | loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), |
240 | makeTestPage(onscrollObserverName, firstFrameObserverName, extraContent), |
241 | "text/html", false); |
242 | |
243 | // We wait for "a couple" of frames for the active tree in CC to stabilize and for pending |
244 | // tree activations to stop clobbering the root scroll layer's scroll offset. This wait |
245 | // doesn't strictly guarantee that but there isn't a good alternative and this seems to |
246 | // work fine. |
247 | firstFrameObserver.waitForEvent(WAIT_TIMEOUT_SECONDS * 1000); |
248 | } |
249 | |
250 | @SmallTest |
251 | @Feature({"AndroidWebView"}) |
252 | public void testUiScrollReflectedInJs() throws Throwable { |
253 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
254 | final ScrollTestContainerView testContainerView = |
255 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
256 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
257 | |
258 | final double deviceDIPScale = |
259 | DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); |
260 | final int targetScrollXCss = 233; |
261 | final int targetScrollYCss = 322; |
262 | final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); |
263 | final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); |
264 | final JavascriptEventObserver onscrollObserver = new JavascriptEventObserver(); |
265 | |
266 | Log.w("AndroidScrollIntegrationTest", String.format("scroll in Js (%d, %d) -> (%d, %d)", |
267 | targetScrollXCss, targetScrollYCss, targetScrollXPix, targetScrollYPix)); |
268 | |
269 | getInstrumentation().runOnMainSync(new Runnable() { |
270 | @Override |
271 | public void run() { |
272 | onscrollObserver.register(testContainerView.getContentViewCore(), |
273 | "onscrollObserver"); |
274 | } |
275 | }); |
276 | |
277 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, "onscrollObserver", ""); |
278 | |
279 | scrollToOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix); |
280 | |
281 | onscrollObserver.waitForEvent(SCROLL_OFFSET_PROPAGATION_TIMEOUT_MS); |
282 | assertScrollInJs(testContainerView.getAwContents(), contentsClient, |
283 | targetScrollXCss, targetScrollYCss); |
284 | } |
285 | |
286 | @SmallTest |
287 | @Feature({"AndroidWebView"}) |
288 | public void testJsScrollReflectedInUi() throws Throwable { |
289 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
290 | final ScrollTestContainerView testContainerView = |
291 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
292 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
293 | |
294 | final double deviceDIPScale = |
295 | DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); |
296 | final int targetScrollXCss = 132; |
297 | final int targetScrollYCss = 243; |
298 | final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); |
299 | final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); |
300 | |
301 | loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), |
302 | makeTestPage(null, null, ""), "text/html", false); |
303 | |
304 | final CallbackHelper onScrollToCallbackHelper = |
305 | testContainerView.getOnScrollToCallbackHelper(); |
306 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
307 | executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, |
308 | String.format("window.scrollTo(%d, %d);", targetScrollXCss, targetScrollYCss)); |
309 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount); |
310 | |
311 | assertScrollOnMainSync(testContainerView, targetScrollXPix, targetScrollYPix); |
312 | } |
313 | |
314 | @SmallTest |
315 | @Feature({"AndroidWebView"}) |
316 | public void testJsScrollCanBeAlteredByUi() throws Throwable { |
317 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
318 | final ScrollTestContainerView testContainerView = |
319 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
320 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
321 | |
322 | final double deviceDIPScale = |
323 | DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); |
324 | final int targetScrollXCss = 132; |
325 | final int targetScrollYCss = 243; |
326 | final int targetScrollXPix = (int) Math.round(targetScrollXCss * deviceDIPScale); |
327 | final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); |
328 | |
329 | final int maxScrollXCss = 101; |
330 | final int maxScrollYCss = 201; |
331 | final int maxScrollXPix = (int) Math.round(maxScrollXCss * deviceDIPScale); |
332 | final int maxScrollYPix = (int) Math.round(maxScrollYCss * deviceDIPScale); |
333 | |
334 | loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), |
335 | makeTestPage(null, null, ""), "text/html", false); |
336 | |
337 | setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); |
338 | |
339 | final CallbackHelper onScrollToCallbackHelper = |
340 | testContainerView.getOnScrollToCallbackHelper(); |
341 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
342 | executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, |
343 | "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")"); |
344 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount); |
345 | |
346 | assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); |
347 | } |
348 | |
349 | @SmallTest |
350 | @Feature({"AndroidWebView"}) |
351 | public void testTouchScrollCanBeAlteredByUi() throws Throwable { |
352 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
353 | final ScrollTestContainerView testContainerView = |
354 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
355 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
356 | |
357 | final int dragSteps = 10; |
358 | final int dragStepSize = 24; |
359 | // Watch out when modifying - if the y or x delta aren't big enough vertical or horizontal |
360 | // scroll snapping will kick in. |
361 | final int targetScrollXPix = dragStepSize * dragSteps; |
362 | final int targetScrollYPix = dragStepSize * dragSteps; |
363 | |
364 | final double deviceDIPScale = |
365 | DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); |
366 | final int maxScrollXPix = 101; |
367 | final int maxScrollYPix = 211; |
368 | // Make sure we can't hit these values simply as a result of scrolling. |
369 | assert (maxScrollXPix % dragStepSize) != 0; |
370 | assert (maxScrollYPix % dragStepSize) != 0; |
371 | final int maxScrollXCss = (int) Math.round(maxScrollXPix / deviceDIPScale); |
372 | final int maxScrollYCss = (int) Math.round(maxScrollYPix / deviceDIPScale); |
373 | |
374 | setMaxScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); |
375 | |
376 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
377 | |
378 | final CallbackHelper onScrollToCallbackHelper = |
379 | testContainerView.getOnScrollToCallbackHelper(); |
380 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
381 | AwTestTouchUtils.dragCompleteView(testContainerView, |
382 | 0, -targetScrollXPix, // these need to be negative as we're scrolling down. |
383 | 0, -targetScrollYPix, |
384 | dragSteps, |
385 | null /* completionLatch */); |
386 | |
387 | for (int i = 1; i <= dragSteps; ++i) { |
388 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i); |
389 | if (checkScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix)) |
390 | break; |
391 | } |
392 | |
393 | assertScrollOnMainSync(testContainerView, maxScrollXPix, maxScrollYPix); |
394 | assertScrollInJs(testContainerView.getAwContents(), contentsClient, |
395 | maxScrollXCss, maxScrollYCss); |
396 | } |
397 | |
398 | @SmallTest |
399 | @Feature({"AndroidWebView"}) |
400 | public void testNoSpuriousOverScrolls() throws Throwable { |
401 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
402 | final ScrollTestContainerView testContainerView = |
403 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
404 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
405 | |
406 | final int dragSteps = 1; |
407 | final int targetScrollYPix = 24; |
408 | |
409 | setMaxScrollOnMainSync(testContainerView, 0, 0); |
410 | |
411 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
412 | |
413 | final CallbackHelper onScrollToCallbackHelper = |
414 | testContainerView.getOnScrollToCallbackHelper(); |
415 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
416 | CountDownLatch scrollingCompleteLatch = new CountDownLatch(1); |
417 | AwTestTouchUtils.dragCompleteView(testContainerView, |
418 | 0, 0, // these need to be negative as we're scrolling down. |
419 | 0, -targetScrollYPix, |
420 | dragSteps, |
421 | scrollingCompleteLatch); |
422 | try { |
423 | scrollingCompleteLatch.await(); |
424 | } catch (InterruptedException ex) { |
425 | // ignore |
426 | } |
427 | assertEquals(scrollToCallCount + 1, onScrollToCallbackHelper.getCallCount()); |
428 | } |
429 | |
430 | @SmallTest |
431 | @Feature({"AndroidWebView"}) |
432 | public void testOverScrollX() throws Throwable { |
433 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
434 | final ScrollTestContainerView testContainerView = |
435 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
436 | final OverScrollByCallbackHelper overScrollByCallbackHelper = |
437 | testContainerView.getOverScrollByCallbackHelper(); |
438 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
439 | |
440 | final int overScrollDeltaX = 30; |
441 | final int oneStep = 1; |
442 | |
443 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
444 | |
445 | // Scroll separately in different dimensions because of vertical/horizontal scroll |
446 | // snap. |
447 | final int overScrollCallCount = overScrollByCallbackHelper.getCallCount(); |
448 | AwTestTouchUtils.dragCompleteView(testContainerView, |
449 | 0, overScrollDeltaX, |
450 | 0, 0, |
451 | oneStep, |
452 | null /* completionLatch */); |
453 | overScrollByCallbackHelper.waitForCallback(overScrollCallCount); |
454 | // Unfortunately the gesture detector seems to 'eat' some number of pixels. For now |
455 | // checking that the value is < 0 (overscroll is reported as negative values) will have to |
456 | // do. |
457 | assertTrue(0 > overScrollByCallbackHelper.getDeltaX()); |
458 | assertEquals(0, overScrollByCallbackHelper.getDeltaY()); |
459 | |
460 | assertScrollOnMainSync(testContainerView, 0, 0); |
461 | } |
462 | |
463 | @SmallTest |
464 | @Feature({"AndroidWebView"}) |
465 | public void testOverScrollY() throws Throwable { |
466 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
467 | final ScrollTestContainerView testContainerView = |
468 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
469 | final OverScrollByCallbackHelper overScrollByCallbackHelper = |
470 | testContainerView.getOverScrollByCallbackHelper(); |
471 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
472 | |
473 | final int overScrollDeltaY = 30; |
474 | final int oneStep = 1; |
475 | |
476 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
477 | |
478 | int overScrollCallCount = overScrollByCallbackHelper.getCallCount(); |
479 | AwTestTouchUtils.dragCompleteView(testContainerView, |
480 | 0, 0, |
481 | 0, overScrollDeltaY, |
482 | oneStep, |
483 | null /* completionLatch */); |
484 | overScrollByCallbackHelper.waitForCallback(overScrollCallCount); |
485 | assertEquals(0, overScrollByCallbackHelper.getDeltaX()); |
486 | assertTrue(0 > overScrollByCallbackHelper.getDeltaY()); |
487 | |
488 | assertScrollOnMainSync(testContainerView, 0, 0); |
489 | } |
490 | |
491 | @SmallTest |
492 | @Feature({"AndroidWebView"}) |
493 | public void testScrollToBottomAtPageScaleX0dot5() throws Throwable { |
494 | // The idea behind this test is to check that scrolling to the bottom on ther renderer side |
495 | // results in the view also reporting as being scrolled to the bottom. |
496 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
497 | final ScrollTestContainerView testContainerView = |
498 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
499 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
500 | |
501 | final int targetScrollXCss = 1000; |
502 | final int targetScrollYCss = 10000; |
503 | |
504 | final String pageHeaders = |
505 | "<meta name=\"viewport\" content=\"width=device-width, initial-scale=0.6\"> " + |
506 | "<style type=\"text/css\"> " + |
507 | " div { " + |
508 | " width:1000px; " + |
509 | " height:10000px; " + |
510 | " background-color: blue; " + |
511 | " } " + |
512 | " body { " + |
513 | " margin: 0px; " + |
514 | " padding: 0px; " + |
515 | " } " + |
516 | "</style> "; |
517 | |
518 | loadDataSync(testContainerView.getAwContents(), contentsClient.getOnPageFinishedHelper(), |
519 | CommonResources.makeHtmlPageFrom(pageHeaders, TEST_PAGE_COMMON_CONTENT), |
520 | "text/html", false); |
521 | |
522 | final double deviceDIPScale = |
523 | DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); |
524 | |
525 | final CallbackHelper onScrollToCallbackHelper = |
526 | testContainerView.getOnScrollToCallbackHelper(); |
527 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
528 | executeJavaScriptAndWaitForResult(testContainerView.getAwContents(), contentsClient, |
529 | "window.scrollTo(" + targetScrollXCss + "," + targetScrollYCss + ")"); |
530 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount); |
531 | |
532 | getInstrumentation().runOnMainSync(new Runnable() { |
533 | @Override |
534 | public void run() { |
535 | AwContents awContents = testContainerView.getAwContents(); |
536 | int maxHorizontal = awContents.computeHorizontalScrollRange() - |
537 | testContainerView.getWidth(); |
538 | int maxVertical = awContents.computeVerticalScrollRange() - |
539 | testContainerView.getHeight(); |
540 | // Due to rounding going from CSS -> physical pixels it is possible that more than |
541 | // one physical pixels corespond to one CSS pixel, which is why we can't do a |
542 | // simple equality test here. |
543 | assertTrue(maxHorizontal - awContents.computeHorizontalScrollOffset() < 3); |
544 | assertTrue(maxVertical - awContents.computeVerticalScrollOffset() < 3); |
545 | } |
546 | }); |
547 | } |
548 | |
549 | @SmallTest |
550 | @Feature({"AndroidWebView"}) |
551 | public void testFlingScroll() throws Throwable { |
552 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
553 | final ScrollTestContainerView testContainerView = |
554 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
555 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
556 | |
557 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
558 | |
559 | assertScrollOnMainSync(testContainerView, 0, 0); |
560 | |
561 | final CallbackHelper onScrollToCallbackHelper = |
562 | testContainerView.getOnScrollToCallbackHelper(); |
563 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
564 | |
565 | getInstrumentation().runOnMainSync(new Runnable() { |
566 | @Override |
567 | public void run() { |
568 | testContainerView.getAwContents().flingScroll(1000, 1000); |
569 | } |
570 | }); |
571 | |
572 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount); |
573 | |
574 | getInstrumentation().runOnMainSync(new Runnable() { |
575 | @Override |
576 | public void run() { |
577 | assertTrue(testContainerView.getScrollX() > 0); |
578 | assertTrue(testContainerView.getScrollY() > 0); |
579 | } |
580 | }); |
581 | } |
582 | |
583 | @SmallTest |
584 | @Feature({"AndroidWebView"}) |
585 | public void testPageDown() throws Throwable { |
586 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
587 | final ScrollTestContainerView testContainerView = |
588 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
589 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
590 | |
591 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
592 | |
593 | assertScrollOnMainSync(testContainerView, 0, 0); |
594 | |
595 | final int maxScrollYPix = runTestOnUiThreadAndGetResult(new Callable<Integer>() { |
596 | @Override |
597 | public Integer call() { |
598 | return (testContainerView.getAwContents().computeVerticalScrollRange() - |
599 | testContainerView.getHeight()); |
600 | } |
601 | }); |
602 | |
603 | final CallbackHelper onScrollToCallbackHelper = |
604 | testContainerView.getOnScrollToCallbackHelper(); |
605 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
606 | |
607 | getInstrumentation().runOnMainSync(new Runnable() { |
608 | @Override |
609 | public void run() { |
610 | testContainerView.getAwContents().pageDown(true); |
611 | } |
612 | }); |
613 | |
614 | // Wait for the animation to hit the bottom of the page. |
615 | for (int i = 1; ; ++i) { |
616 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i); |
617 | if (checkScrollOnMainSync(testContainerView, 0, maxScrollYPix)) |
618 | break; |
619 | } |
620 | } |
621 | |
622 | @SmallTest |
623 | @Feature({"AndroidWebView"}) |
624 | public void testPageUp() throws Throwable { |
625 | final TestAwContentsClient contentsClient = new TestAwContentsClient(); |
626 | final ScrollTestContainerView testContainerView = |
627 | (ScrollTestContainerView) createAwTestContainerViewOnMainSync(contentsClient); |
628 | enableJavaScriptOnUiThread(testContainerView.getAwContents()); |
629 | |
630 | final double deviceDIPScale = |
631 | DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale(); |
632 | final int targetScrollYCss = 243; |
633 | final int targetScrollYPix = (int) Math.round(targetScrollYCss * deviceDIPScale); |
634 | |
635 | loadTestPageAndWaitForFirstFrame(testContainerView, contentsClient, null, ""); |
636 | |
637 | assertScrollOnMainSync(testContainerView, 0, 0); |
638 | |
639 | scrollToOnMainSync(testContainerView, 0, targetScrollYPix); |
640 | |
641 | final CallbackHelper onScrollToCallbackHelper = |
642 | testContainerView.getOnScrollToCallbackHelper(); |
643 | final int scrollToCallCount = onScrollToCallbackHelper.getCallCount(); |
644 | |
645 | getInstrumentation().runOnMainSync(new Runnable() { |
646 | @Override |
647 | public void run() { |
648 | testContainerView.getAwContents().pageUp(true); |
649 | } |
650 | }); |
651 | |
652 | // Wait for the animation to hit the bottom of the page. |
653 | for (int i = 1; ; ++i) { |
654 | onScrollToCallbackHelper.waitForCallback(scrollToCallCount, i); |
655 | if (checkScrollOnMainSync(testContainerView, 0, 0)) |
656 | break; |
657 | } |
658 | } |
659 | } |