Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / android_webview / javatests / src / org / chromium / android_webview / test / AndroidViewIntegrationTest.java
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.test.suitebuilder.annotation.SmallTest;
8 import android.view.View;
9 import android.view.ViewGroup.LayoutParams;
10 import android.widget.LinearLayout;
11
12 import org.chromium.android_webview.AwContents;
13 import org.chromium.android_webview.AwContentsClient;
14 import org.chromium.android_webview.AwLayoutSizer;
15 import org.chromium.android_webview.test.util.CommonResources;
16 import org.chromium.base.test.util.Feature;
17 import org.chromium.content.browser.test.util.CallbackHelper;
18 import org.chromium.ui.gfx.DeviceDisplayInfo;
19
20 import java.util.concurrent.atomic.AtomicReference;
21
22 /**
23  * Tests for certain edge cases related to integrating with the Android view system.
24  */
25 public class AndroidViewIntegrationTest extends AwTestBase {
26     private static final int CONTENT_SIZE_CHANGE_STABILITY_TIMEOUT_MS = 1000;
27
28     private static class OnContentSizeChangedHelper extends CallbackHelper {
29         private int mWidth;
30         private int mHeight;
31
32         public int getWidth() {
33             assert getCallCount() > 0;
34             return mWidth;
35         }
36
37         public int getHeight() {
38             assert getCallCount() > 0;
39             return mHeight;
40         }
41
42         public void onContentSizeChanged(int widthCss, int heightCss) {
43             mWidth = widthCss;
44             mHeight = heightCss;
45             notifyCalled();
46         }
47     }
48
49     private OnContentSizeChangedHelper mOnContentSizeChangedHelper =
50         new OnContentSizeChangedHelper();
51     private CallbackHelper mOnPageScaleChangedHelper = new CallbackHelper();
52
53     private class TestAwLayoutSizer extends AwLayoutSizer {
54         @Override
55         public void onContentSizeChanged(int widthCss, int heightCss) {
56             super.onContentSizeChanged(widthCss, heightCss);
57             if (mOnContentSizeChangedHelper != null)
58                 mOnContentSizeChangedHelper.onContentSizeChanged(widthCss, heightCss);
59         }
60
61         @Override
62         public void onPageScaleChanged(float pageScaleFactor) {
63             super.onPageScaleChanged(pageScaleFactor);
64             if (mOnPageScaleChangedHelper != null)
65                 mOnPageScaleChangedHelper.notifyCalled();
66         }
67     }
68
69     @Override
70     protected TestDependencyFactory createTestDependencyFactory() {
71         return new TestDependencyFactory() {
72             @Override
73             public AwLayoutSizer createLayoutSizer() {
74                 return new TestAwLayoutSizer();
75             }
76         };
77     }
78
79     final LinearLayout.LayoutParams mWrapContentLayoutParams =
80         new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
81
82     private AwTestContainerView createCustomTestContainerViewOnMainSync(
83             final AwContentsClient awContentsClient, final int visibility) throws Exception {
84         final AtomicReference<AwTestContainerView> testContainerView =
85                 new AtomicReference<AwTestContainerView>();
86         getInstrumentation().runOnMainSync(new Runnable() {
87             @Override
88             public void run() {
89                 testContainerView.set(createAwTestContainerView(awContentsClient));
90                 testContainerView.get().setLayoutParams(mWrapContentLayoutParams);
91                 testContainerView.get().setVisibility(visibility);
92             }
93         });
94         return testContainerView.get();
95     }
96
97     private AwTestContainerView createDetachedTestContainerViewOnMainSync(
98             final AwContentsClient awContentsClient) {
99         final AtomicReference<AwTestContainerView> testContainerView =
100                 new AtomicReference<AwTestContainerView>();
101         getInstrumentation().runOnMainSync(new Runnable() {
102             @Override
103             public void run() {
104                 testContainerView.set(createDetachedAwTestContainerView(awContentsClient));
105             }
106         });
107         return testContainerView.get();
108     }
109
110     private void assertZeroHeight(final AwTestContainerView testContainerView) throws Throwable {
111         // Make sure the test isn't broken by the view having a non-zero height.
112         getInstrumentation().runOnMainSync(new Runnable() {
113             @Override
114             public void run() {
115                 assertEquals(0, testContainerView.getHeight());
116             }
117         });
118     }
119
120     private int getRootLayoutWidthOnMainThread() throws Exception {
121         final AtomicReference<Integer> width = new AtomicReference<Integer>();
122         getInstrumentation().runOnMainSync(new Runnable() {
123             @Override
124             public void run() {
125                 width.set(Integer.valueOf(getActivity().getRootLayoutWidth()));
126             }
127         });
128         return width.get();
129     }
130
131     /**
132      * This checks for issues related to loading content into a 0x0 view.
133      *
134      * A 0x0 sized view is common if the WebView is set to wrap_content and newly created. The
135      * expected behavior is for the WebView to expand after some content is loaded.
136      * In Chromium it would be valid to not load or render content into a WebContents with a 0x0
137      * view (since the user can't see it anyway) and only do so after the view's size is non-zero.
138      * Such behavior is unacceptable for the WebView and this test is to ensure that such behavior
139      * is not re-introduced.
140      */
141     @SmallTest
142     @Feature({"AndroidWebView"})
143     public void testZeroByZeroViewLoadsContent() throws Throwable {
144         final TestAwContentsClient contentsClient = new TestAwContentsClient();
145         final AwTestContainerView testContainerView = createCustomTestContainerViewOnMainSync(
146                 contentsClient, View.VISIBLE);
147         assertZeroHeight(testContainerView);
148
149         final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount();
150         final int pageScaleChangeCallCount = mOnPageScaleChangedHelper.getCallCount();
151         loadUrlAsync(testContainerView.getAwContents(), CommonResources.ABOUT_HTML);
152         mOnPageScaleChangedHelper.waitForCallback(pageScaleChangeCallCount);
153         mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount);
154         assertTrue(mOnContentSizeChangedHelper.getHeight() > 0);
155     }
156
157     /**
158      * Check that a content size change notification is issued when the view is invisible.
159      *
160      * This makes sure that any optimizations related to the view's visibility don't inhibit
161      * the ability to load pages. Many applications keep the WebView hidden when it's loading.
162      */
163     @SmallTest
164     @Feature({"AndroidWebView"})
165     public void testInvisibleViewLoadsContent() throws Throwable {
166         final TestAwContentsClient contentsClient = new TestAwContentsClient();
167         final AwTestContainerView testContainerView = createCustomTestContainerViewOnMainSync(
168                 contentsClient, View.INVISIBLE);
169         assertZeroHeight(testContainerView);
170
171         final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount();
172         final int pageScaleChangeCallCount = mOnPageScaleChangedHelper.getCallCount();
173         loadUrlAsync(testContainerView.getAwContents(), CommonResources.ABOUT_HTML);
174         mOnPageScaleChangedHelper.waitForCallback(pageScaleChangeCallCount);
175         mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount);
176         assertTrue(mOnContentSizeChangedHelper.getHeight() > 0);
177
178         getInstrumentation().runOnMainSync(new Runnable() {
179             @Override
180             public void run() {
181                 assertEquals(View.INVISIBLE, testContainerView.getVisibility());
182             }
183         });
184     }
185
186     /**
187      * Check that a content size change notification is sent even if the WebView is off screen.
188      */
189     @SmallTest
190     @Feature({"AndroidWebView"})
191     public void testDisconnectedViewLoadsContent() throws Throwable {
192         final TestAwContentsClient contentsClient = new TestAwContentsClient();
193         final AwTestContainerView testContainerView =
194             createDetachedTestContainerViewOnMainSync(contentsClient);
195         assertZeroHeight(testContainerView);
196
197         final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount();
198         final int pageScaleChangeCallCount = mOnPageScaleChangedHelper.getCallCount();
199         loadUrlAsync(testContainerView.getAwContents(), CommonResources.ABOUT_HTML);
200         mOnPageScaleChangedHelper.waitForCallback(pageScaleChangeCallCount);
201         mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount);
202         assertTrue(mOnContentSizeChangedHelper.getHeight() > 0);
203     }
204
205     private String makeHtmlPageOfSize(int widthCss, int heightCss, boolean heightPercent) {
206         String content = "<div class=\"normal\">a</div>";
207         if (heightPercent)
208             content += "<div class=\"heightPercent\"></div>";
209         return CommonResources.makeHtmlPageFrom(
210             "<style type=\"text/css\">" +
211                 "body { margin:0px; padding:0px; } " +
212                 ".normal { " +
213                    "width:" + widthCss + "px; " +
214                    "height:" + heightCss + "px; " +
215                    "background-color: red; " +
216                  "} " +
217                  ".heightPercent { " +
218                    "height: 150%; " +
219                    "background-color: blue; " +
220                  "} " +
221             "</style>", content);
222     }
223
224     private void waitForContentSizeToChangeTo(OnContentSizeChangedHelper helper, int callCount,
225             int widthCss, int heightCss) throws Exception {
226         final int maxSizeChangeNotificationsToWaitFor = 5;
227         for (int i = 1; i <= maxSizeChangeNotificationsToWaitFor; i++) {
228             helper.waitForCallback(callCount, i);
229             if ((heightCss == -1 || helper.getHeight() == heightCss) &&
230                     (widthCss == -1 || helper.getWidth() == widthCss)) {
231                 break;
232             }
233             // This means that we hit the max number of iterations but the expected contents size
234             // wasn't reached.
235             assertTrue(i != maxSizeChangeNotificationsToWaitFor);
236         }
237     }
238
239     private void loadPageOfSizeAndWaitForSizeChange(AwContents awContents,
240             OnContentSizeChangedHelper helper, int widthCss, int heightCss,
241             boolean heightPercent) throws Exception {
242
243         final String htmlData = makeHtmlPageOfSize(widthCss, heightCss, heightPercent);
244         final int contentSizeChangeCallCount = helper.getCallCount();
245         loadDataAsync(awContents, htmlData, "text/html", false);
246
247         waitForContentSizeToChangeTo(helper, contentSizeChangeCallCount, widthCss, heightCss);
248     }
249
250     @SmallTest
251     @Feature({"AndroidWebView"})
252     public void testSizeUpdateWhenDetached() throws Throwable {
253         final TestAwContentsClient contentsClient = new TestAwContentsClient();
254         final AwTestContainerView testContainerView = createDetachedTestContainerViewOnMainSync(
255                 contentsClient);
256         assertZeroHeight(testContainerView);
257
258         final int contentWidthCss = 142;
259         final int contentHeightCss = 180;
260
261         loadPageOfSizeAndWaitForSizeChange(testContainerView.getAwContents(),
262                 mOnContentSizeChangedHelper, contentWidthCss, contentHeightCss, false);
263     }
264
265     public void waitForNoLayoutsPending() throws InterruptedException {
266         // This is to make sure that there are no more pending size change notifications. Ideally
267         // we'd assert that the renderer is idle (has no pending layout passes) but that would
268         // require quite a bit of plumbing, so we just wait a bit and make sure the size hadn't
269         // changed.
270         Thread.sleep(CONTENT_SIZE_CHANGE_STABILITY_TIMEOUT_MS);
271     }
272
273     @SmallTest
274     @Feature({"AndroidWebView"})
275     public void testAbsolutePositionContributesToContentSize() throws Throwable {
276         final TestAwContentsClient contentsClient = new TestAwContentsClient();
277         final AwTestContainerView testContainerView = createDetachedTestContainerViewOnMainSync(
278                 contentsClient);
279         assertZeroHeight(testContainerView);
280
281         final int widthCss = 142;
282         final int heightCss = 180;
283
284         final String htmlData = CommonResources.makeHtmlPageFrom(
285             "<style type=\"text/css\">" +
286                 "body { margin:0px; padding:0px; } " +
287                 "div { " +
288                    "position: absolute; " +
289                    "width:" + widthCss + "px; " +
290                    "height:" + heightCss + "px; " +
291                    "background-color: red; " +
292                  "} " +
293             "</style>", "<div>a</div>");
294
295         final int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount();
296         loadDataAsync(testContainerView.getAwContents(), htmlData, "text/html", false);
297
298         waitForContentSizeToChangeTo(mOnContentSizeChangedHelper, contentSizeChangeCallCount,
299                 widthCss, heightCss);
300     }
301
302     @SmallTest
303     @Feature({"AndroidWebView"})
304     public void testViewSizedCorrectlyInWrapContentMode() throws Throwable {
305         final TestAwContentsClient contentsClient = new TestAwContentsClient();
306         final AwTestContainerView testContainerView = createCustomTestContainerViewOnMainSync(
307                 contentsClient, View.VISIBLE);
308         assertZeroHeight(testContainerView);
309
310         final double deviceDIPScale =
311             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
312
313         final int contentWidthCss = 142;
314         final int contentHeightCss = 180;
315
316         // In wrap-content mode the AwLayoutSizer will size the view to be as wide as the parent
317         // view.
318         final int expectedWidthCss =
319             (int) Math.ceil(getRootLayoutWidthOnMainThread() / deviceDIPScale);
320         final int expectedHeightCss = contentHeightCss;
321
322         loadPageOfSizeAndWaitForSizeChange(testContainerView.getAwContents(),
323                 mOnContentSizeChangedHelper, expectedWidthCss, expectedHeightCss, false);
324
325         waitForNoLayoutsPending();
326         assertEquals(expectedWidthCss, mOnContentSizeChangedHelper.getWidth());
327         assertEquals(expectedHeightCss, mOnContentSizeChangedHelper.getHeight());
328     }
329
330     @SmallTest
331     @Feature({"AndroidWebView"})
332     public void testViewSizedCorrectlyInWrapContentModeWithDynamicContents() throws Throwable {
333         final TestAwContentsClient contentsClient = new TestAwContentsClient();
334         final AwTestContainerView testContainerView = createCustomTestContainerViewOnMainSync(
335                 contentsClient, View.VISIBLE);
336         assertZeroHeight(testContainerView);
337
338         final double deviceDIPScale =
339             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
340
341         final int contentWidthCss = 142;
342         final int contentHeightCss = 180;
343
344         final int expectedWidthCss =
345             (int) Math.ceil(getRootLayoutWidthOnMainThread() / deviceDIPScale);
346         final int expectedHeightCss = contentHeightCss;
347
348         loadPageOfSizeAndWaitForSizeChange(testContainerView.getAwContents(),
349                 mOnContentSizeChangedHelper, expectedWidthCss, contentHeightCss, true);
350
351         waitForNoLayoutsPending();
352         assertEquals(expectedWidthCss, mOnContentSizeChangedHelper.getWidth());
353         assertEquals(expectedHeightCss, mOnContentSizeChangedHelper.getHeight());
354     }
355
356     @SmallTest
357     @Feature({"AndroidWebView"})
358     public void testReceivingSizeAfterLoadUpdatesLayout() throws Throwable {
359         final TestAwContentsClient contentsClient = new TestAwContentsClient();
360         final AwTestContainerView testContainerView = createDetachedTestContainerViewOnMainSync(
361                 contentsClient);
362         final AwContents awContents = testContainerView.getAwContents();
363
364         final double deviceDIPScale =
365             DeviceDisplayInfo.create(testContainerView.getContext()).getDIPScale();
366         final int physicalWidth = 600;
367         final int spanWidth = 42;
368         final int expectedWidthCss =
369             (int) Math.ceil(physicalWidth / deviceDIPScale);
370
371         StringBuilder htmlBuilder = new StringBuilder("<html><body style='margin:0px;'>");
372         final String spanBlock =
373             "<span style='width: " + spanWidth + "px; display: inline-block;'>a</span>";
374         for (int i = 0; i < 10; ++i) {
375             htmlBuilder.append(spanBlock);
376         }
377         htmlBuilder.append("</body></html>");
378
379         int contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount();
380         loadDataAsync(awContents, htmlBuilder.toString(), "text/html", false);
381         // Because we're loading the contents into a detached WebView its layout size is 0x0 and as
382         // a result of that the paragraph will be formated such that each word is on a separate
383         // line.
384         waitForContentSizeToChangeTo(mOnContentSizeChangedHelper, contentSizeChangeCallCount,
385                 spanWidth, -1);
386
387         final int narrowLayoutHeight = mOnContentSizeChangedHelper.getHeight();
388
389         contentSizeChangeCallCount = mOnContentSizeChangedHelper.getCallCount();
390         getInstrumentation().runOnMainSync(new Runnable() {
391             @Override
392             public void run() {
393                 testContainerView.onSizeChanged(physicalWidth, 0, 0, 0);
394             }
395         });
396         mOnContentSizeChangedHelper.waitForCallback(contentSizeChangeCallCount);
397
398         // As a result of calling the onSizeChanged method the layout size should be updated to
399         // match the width of the webview and the text we previously loaded should reflow making the
400         // contents width match the WebView width.
401         assertEquals(expectedWidthCss, mOnContentSizeChangedHelper.getWidth());
402         assertTrue(mOnContentSizeChangedHelper.getHeight() < narrowLayoutHeight);
403         assertTrue(mOnContentSizeChangedHelper.getHeight() > 0);
404     }
405 }