e81a5563cd76518f97def1c95e4fc7ea32b82c25
[platform/framework/web/crosswalk.git] / src / android_webview / javatests / src / org / chromium / android_webview / test / AwContentsTest.java
1 // Copyright 2012 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.graphics.Bitmap;
8 import android.graphics.BitmapFactory;
9 import android.graphics.Canvas;
10 import android.os.Handler;
11 import android.os.Looper;
12 import android.os.Message;
13 import android.test.UiThreadTest;
14 import android.test.suitebuilder.annotation.LargeTest;
15 import android.test.suitebuilder.annotation.SmallTest;
16 import android.util.Pair;
17 import android.view.KeyEvent;
18 import android.view.View;
19
20 import org.apache.http.Header;
21 import org.apache.http.HttpRequest;
22 import org.chromium.android_webview.AwContents;
23 import org.chromium.android_webview.AwSettings;
24 import org.chromium.android_webview.test.TestAwContentsClient.OnDownloadStartHelper;
25 import org.chromium.android_webview.test.util.CommonResources;
26 import org.chromium.base.test.util.Feature;
27 import org.chromium.content.browser.test.util.CallbackHelper;
28 import org.chromium.content_public.browser.LoadUrlParams;
29 import org.chromium.net.test.util.TestWebServer;
30
31 import java.io.InputStream;
32 import java.net.URL;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.concurrent.Callable;
38 import java.util.concurrent.Semaphore;
39 import java.util.concurrent.TimeUnit;
40 import java.util.concurrent.atomic.AtomicInteger;
41
42 /**
43  * AwContents tests.
44  */
45 public class AwContentsTest extends AwTestBase {
46
47     private TestAwContentsClient mContentsClient = new TestAwContentsClient();
48
49     @SmallTest
50     @Feature({"AndroidWebView"})
51     @UiThreadTest
52     public void testCreateDestroy() throws Throwable {
53         // NOTE this test runs on UI thread, so we cannot call any async methods.
54         createAwTestContainerView(mContentsClient).getAwContents().destroy();
55     }
56
57     @SmallTest
58     @Feature({"AndroidWebView"})
59     public void testCreateLoadPageDestroy() throws Throwable {
60         AwTestContainerView awTestContainerView =
61                 createAwTestContainerViewOnMainSync(mContentsClient);
62         loadUrlSync(awTestContainerView.getAwContents(),
63                 mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML);
64         destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
65         // It should be safe to call destroy multiple times.
66         destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
67     }
68
69     @LargeTest
70     @Feature({"AndroidWebView"})
71     public void testCreateLoadDestroyManyTimes() throws Throwable {
72         for (int i = 0; i < 10; ++i) {
73             AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
74             AwContents awContents = testView.getAwContents();
75
76             loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
77             destroyAwContentsOnMainSync(awContents);
78         }
79     }
80
81     @LargeTest
82     @Feature({"AndroidWebView"})
83     public void testCreateLoadDestroyManyAtOnce() throws Throwable {
84         AwTestContainerView views[] = new AwTestContainerView[10];
85
86         for (int i = 0; i < views.length; ++i) {
87             views[i] = createAwTestContainerViewOnMainSync(mContentsClient);
88             loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(),
89                     "about:blank");
90         }
91
92         for (int i = 0; i < views.length; ++i) {
93             destroyAwContentsOnMainSync(views[i].getAwContents());
94             views[i] = null;
95         }
96     }
97
98     @SmallTest
99     @Feature({"AndroidWebView"})
100     @UiThreadTest
101     public void testWebViewApisFailGracefullyAfterDestruction() throws Throwable {
102         AwContents awContents =
103                 createAwTestContainerView(mContentsClient).getAwContents();
104         awContents.destroy();
105
106         assertNull(awContents.getWebContents());
107         assertNull(awContents.getContentViewCore());
108         assertNull(awContents.getNavigationController());
109
110         // The documentation for WebView#destroy() reads "This method should be called
111         // after this WebView has been removed from the view system. No other methods
112         // may be called on this WebView after destroy".
113         // However, some apps do not respect that restriction so we need to ensure that
114         // we fail gracefully and do not crash when APIs are invoked after destruction.
115         // Due to the large number of APIs we only test a representative selection here.
116         awContents.clearHistory();
117         awContents.loadUrl(new LoadUrlParams("http://www.google.com"));
118         awContents.findAllAsync("search");
119         assertNull(awContents.getUrl());
120         assertNull(awContents.getContentSettings());
121         assertFalse(awContents.canGoBack());
122         awContents.disableJavascriptInterfacesInspection();
123         awContents.invokeZoomPicker();
124         awContents.onResume();
125         awContents.stopLoading();
126         awContents.onWindowVisibilityChanged(View.VISIBLE);
127         awContents.requestFocus();
128         awContents.isMultiTouchZoomSupported();
129         awContents.setOverScrollMode(View.OVER_SCROLL_NEVER);
130         awContents.pauseTimers();
131         awContents.onContainerViewScrollChanged(200, 200, 100, 100);
132         awContents.computeScroll();
133         awContents.onMeasure(100, 100);
134         awContents.onDraw(new Canvas());
135         awContents.getMostRecentProgress();
136         assertEquals(0, awContents.computeHorizontalScrollOffset());
137         assertEquals(0, awContents.getContentWidthCss());
138         awContents.onKeyUp(KeyEvent.KEYCODE_BACK,
139                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
140     }
141
142     @LargeTest
143     @Feature({"AndroidWebView"})
144     public void testCreateAndGcManyTimes() throws Throwable {
145         final int concurrentInstances = 4;
146         final int repetitions = 16;
147         // The system retains a strong ref to the last focused view (in InputMethodManager)
148         // so allow for 1 'leaked' instance.
149         final int maxIdleInstances = 1;
150
151         System.gc();
152
153         pollOnUiThread(new Callable<Boolean>() {
154             @Override
155             public Boolean call() {
156                 return AwContents.getNativeInstanceCount() <= maxIdleInstances;
157             }
158         });
159         for (int i = 0; i < repetitions; ++i) {
160             for (int j = 0; j < concurrentInstances; ++j) {
161                 AwTestContainerView view = createAwTestContainerViewOnMainSync(mContentsClient);
162                 loadUrlAsync(view.getAwContents(), "about:blank");
163             }
164             assertTrue(AwContents.getNativeInstanceCount() >= concurrentInstances);
165             assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * concurrentInstances);
166             runTestOnUiThread(new Runnable() {
167                 @Override
168                 public void run() {
169                     getActivity().removeAllViews();
170                 }
171             });
172         }
173
174         System.gc();
175
176         pollOnUiThread(new Callable<Boolean>() {
177             @Override
178             public Boolean call() {
179                 return AwContents.getNativeInstanceCount() <= maxIdleInstances;
180             }
181         });
182     }
183
184     @SmallTest
185     @Feature({"AndroidWebView"})
186     public void testUseAwSettingsAfterDestroy() throws Throwable {
187         AwTestContainerView awTestContainerView =
188                 createAwTestContainerViewOnMainSync(mContentsClient);
189         AwSettings awSettings = getAwSettingsOnUiThread(awTestContainerView.getAwContents());
190         loadUrlSync(awTestContainerView.getAwContents(),
191                 mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML);
192         destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
193
194         // AwSettings should still be usable even after native side is destroyed.
195         String newFontFamily = "serif";
196         awSettings.setStandardFontFamily(newFontFamily);
197         assertEquals(newFontFamily, awSettings.getStandardFontFamily());
198         boolean newBlockNetworkLoads = !awSettings.getBlockNetworkLoads();
199         awSettings.setBlockNetworkLoads(newBlockNetworkLoads);
200         assertEquals(newBlockNetworkLoads, awSettings.getBlockNetworkLoads());
201     }
202
203     private int callDocumentHasImagesSync(final AwContents awContents)
204             throws Throwable, InterruptedException {
205         // Set up a container to hold the result object and a semaphore to
206         // make the test wait for the result.
207         final AtomicInteger val = new AtomicInteger();
208         final Semaphore s = new Semaphore(0);
209         final Message msg = Message.obtain(new Handler(Looper.getMainLooper()) {
210             @Override
211             public void handleMessage(Message msg) {
212                 val.set(msg.arg1);
213                 s.release();
214             }
215         });
216         runTestOnUiThread(new Runnable() {
217             @Override
218             public void run() {
219               awContents.documentHasImages(msg);
220             }
221         });
222         assertTrue(s.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
223         int result = val.get();
224         return result;
225     }
226
227     @SmallTest
228     @Feature({"AndroidWebView"})
229     public void testDocumentHasImages() throws Throwable {
230         AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
231         AwContents awContents = testView.getAwContents();
232
233         final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper();
234
235         final String mime = "text/html";
236         final String emptyDoc = "<head/><body/>";
237         final String imageDoc = "<head/><body><img/><img/></body>";
238
239         // Make sure a document that does not have images returns 0
240         loadDataSync(awContents, loadHelper, emptyDoc, mime, false);
241         int result = callDocumentHasImagesSync(awContents);
242         assertEquals(0, result);
243
244         // Make sure a document that does have images returns 1
245         loadDataSync(awContents, loadHelper, imageDoc, mime, false);
246         result = callDocumentHasImagesSync(awContents);
247         assertEquals(1, result);
248     }
249
250     @SmallTest
251     @Feature({"AndroidWebView"})
252     public void testClearCacheMemoryAndDisk() throws Throwable {
253         final AwTestContainerView testContainer =
254                 createAwTestContainerViewOnMainSync(mContentsClient);
255         final AwContents awContents = testContainer.getAwContents();
256
257         TestWebServer webServer = TestWebServer.start();
258         try {
259             final String pagePath = "/clear_cache_test.html";
260             List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
261             // Set Cache-Control headers to cache this request. One century should be long enough.
262             headers.add(Pair.create("Cache-Control", "max-age=3153600000"));
263             headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT"));
264             final String pageUrl = webServer.setResponse(
265                     pagePath, "<html><body>foo</body></html>", headers);
266
267             // First load to populate cache.
268             clearCacheOnUiThread(awContents, true);
269             loadUrlSync(awContents,
270                         mContentsClient.getOnPageFinishedHelper(),
271                         pageUrl);
272             assertEquals(1, webServer.getRequestCount(pagePath));
273
274             // Load about:blank so next load is not treated as reload by webkit and force
275             // revalidate with the server.
276             loadUrlSync(awContents,
277                         mContentsClient.getOnPageFinishedHelper(),
278                         "about:blank");
279
280             // No clearCache call, so should be loaded from cache.
281             loadUrlSync(awContents,
282                         mContentsClient.getOnPageFinishedHelper(),
283                         pageUrl);
284             assertEquals(1, webServer.getRequestCount(pagePath));
285
286             // Same as above.
287             loadUrlSync(awContents,
288                         mContentsClient.getOnPageFinishedHelper(),
289                         "about:blank");
290
291             // Clear cache, so should hit server again.
292             clearCacheOnUiThread(awContents, true);
293             loadUrlSync(awContents,
294                         mContentsClient.getOnPageFinishedHelper(),
295                         pageUrl);
296             assertEquals(2, webServer.getRequestCount(pagePath));
297         } finally {
298             webServer.shutdown();
299         }
300     }
301
302     @SmallTest
303     @Feature({"AndroidWebView"})
304     public void testClearCacheInQuickSuccession() throws Throwable {
305         final AwTestContainerView testContainer =
306                 createAwTestContainerViewOnMainSync(new TestAwContentsClient());
307         final AwContents awContents = testContainer.getAwContents();
308
309         runTestOnUiThread(new Runnable() {
310             @Override
311             public void run() {
312                 for (int i = 0; i < 10; ++i) {
313                     awContents.clearCache(true);
314                 }
315             }
316         });
317     }
318
319     @SmallTest
320     @Feature({"AndroidWebView"})
321     public void testGetFavicon() throws Throwable {
322         AwContents.setShouldDownloadFavicons();
323         final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
324         final AwContents awContents = testView.getAwContents();
325
326         TestWebServer webServer = TestWebServer.start();
327         try {
328             final String faviconUrl = webServer.setResponseBase64(
329                     "/" + CommonResources.FAVICON_FILENAME, CommonResources.FAVICON_DATA_BASE64,
330                     CommonResources.getImagePngHeaders(false));
331             final String pageUrl = webServer.setResponse("/favicon.html",
332                     CommonResources.FAVICON_STATIC_HTML, null);
333
334             // The getFavicon will return the right icon a certain time after
335             // the page load completes which makes it slightly hard to test.
336             final Bitmap defaultFavicon = awContents.getFavicon();
337
338             getAwSettingsOnUiThread(awContents).setImagesEnabled(true);
339             loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
340
341             pollOnUiThread(new Callable<Boolean>() {
342                 @Override
343                 public Boolean call() {
344                     return awContents.getFavicon() != null &&
345                         !awContents.getFavicon().sameAs(defaultFavicon);
346                 }
347             });
348
349             final Object originalFaviconSource = (new URL(faviconUrl)).getContent();
350             final Bitmap originalFavicon =
351                 BitmapFactory.decodeStream((InputStream) originalFaviconSource);
352             assertNotNull(originalFavicon);
353
354             assertTrue(awContents.getFavicon().sameAs(originalFavicon));
355
356         } finally {
357             webServer.shutdown();
358         }
359     }
360
361     @Feature({"AndroidWebView", "Downloads"})
362     @SmallTest
363     public void testDownload() throws Throwable {
364         AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
365         AwContents awContents = testView.getAwContents();
366
367         final String data = "download data";
368         final String contentDisposition = "attachment;filename=\"download.txt\"";
369         final String mimeType = "text/plain";
370
371         List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
372         downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
373         downloadHeaders.add(Pair.create("Content-Type", mimeType));
374         downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
375
376         TestWebServer webServer = TestWebServer.start();
377         try {
378             final String pageUrl = webServer.setResponse(
379                     "/download.txt", data, downloadHeaders);
380             final OnDownloadStartHelper downloadStartHelper =
381                 mContentsClient.getOnDownloadStartHelper();
382             final int callCount = downloadStartHelper.getCallCount();
383             loadUrlAsync(awContents, pageUrl);
384             downloadStartHelper.waitForCallback(callCount);
385
386             assertEquals(pageUrl, downloadStartHelper.getUrl());
387             assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
388             assertEquals(mimeType, downloadStartHelper.getMimeType());
389             assertEquals(data.length(), downloadStartHelper.getContentLength());
390         } finally {
391             webServer.shutdown();
392         }
393     }
394
395     @Feature({"AndroidWebView", "setNetworkAvailable"})
396     @SmallTest
397     public void testSetNetworkAvailable() throws Throwable {
398         AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
399         AwContents awContents = testView.getAwContents();
400         String script = "navigator.onLine";
401
402         enableJavaScriptOnUiThread(awContents);
403         loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
404
405         // Default to "online".
406         assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
407               script));
408
409         // Forcing "offline".
410         setNetworkAvailableOnUiThread(awContents, false);
411         assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
412               script));
413
414         // Forcing "online".
415         setNetworkAvailableOnUiThread(awContents, true);
416         assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
417               script));
418     }
419
420
421     static class JavaScriptObject {
422         private CallbackHelper mCallbackHelper;
423         public JavaScriptObject(CallbackHelper callbackHelper) {
424             mCallbackHelper = callbackHelper;
425         }
426
427         public void run() {
428             mCallbackHelper.notifyCalled();
429         }
430     }
431
432     @Feature({"AndroidWebView", "JavaBridge"})
433     @SmallTest
434     public void testJavaBridge() throws Throwable {
435         final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
436         final CallbackHelper callback = new CallbackHelper();
437
438         runTestOnUiThread(new Runnable() {
439             @Override
440             public void run() {
441                 AwContents awContents = testView.getAwContents();
442                 AwSettings awSettings = awContents.getSettings();
443                 awSettings.setJavaScriptEnabled(true);
444                 awContents.addPossiblyUnsafeJavascriptInterface(
445                         new JavaScriptObject(callback), "bridge", null);
446                 awContents.evaluateJavaScript("javascript:window.bridge.run();", null);
447             }
448         });
449         callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
450     }
451
452     @Feature({"AndroidWebView"})
453     @SmallTest
454     public void testEscapingOfErrorPage() throws Throwable {
455         AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
456         AwContents awContents = testView.getAwContents();
457         String script = "window.failed == true";
458
459         enableJavaScriptOnUiThread(awContents);
460         CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
461         int currentCallCount = onPageFinishedHelper.getCallCount();
462         loadUrlAsync(awContents,
463                 "file:///file-that-does-not-exist#<script>window.failed = true;</script>");
464         onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
465                                              TimeUnit.MILLISECONDS);
466
467         assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
468                 script));
469     }
470
471     @Feature({"AndroidWebView"})
472     @SmallTest
473     public void testCanInjectHeaders() throws Throwable {
474         final AwTestContainerView testContainer =
475                 createAwTestContainerViewOnMainSync(mContentsClient);
476         final AwContents awContents = testContainer.getAwContents();
477
478         TestWebServer webServer = TestWebServer.start();
479         try {
480             final String pagePath = "/test_can_inject_headers.html";
481             final String pageUrl = webServer.setResponse(
482                     pagePath, "<html><body>foo</body></html>", null);
483
484             final Map<String, String> extraHeaders = new HashMap<String, String>();
485             extraHeaders.put("Referer", "foo");
486             extraHeaders.put("X-foo", "bar");
487             loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(),
488                     webServer.getResponseUrl(pagePath), extraHeaders);
489
490             assertEquals(1, webServer.getRequestCount(pagePath));
491
492             HttpRequest request = webServer.getLastRequest(pagePath);
493             assertNotNull(request);
494
495             for (Map.Entry<String, String> value : extraHeaders.entrySet()) {
496                 String header = value.getKey();
497                 Header[] matchingHeaders = request.getHeaders(header);
498                 assertEquals("header " + header + " not found", 1, matchingHeaders.length);
499                 assertEquals(value.getValue(), matchingHeaders[0].getValue());
500             }
501         } finally {
502             webServer.shutdown();
503         }
504     }
505
506 }