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