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.
5 package org.chromium.android_webview.test;
7 import android.graphics.Bitmap;
8 import android.graphics.BitmapFactory;
9 import android.os.Handler;
10 import android.os.Looper;
11 import android.os.Message;
12 import android.test.UiThreadTest;
13 import android.test.suitebuilder.annotation.LargeTest;
14 import android.test.suitebuilder.annotation.SmallTest;
15 import android.util.Pair;
17 import org.apache.http.Header;
18 import org.apache.http.HttpRequest;
20 import org.chromium.android_webview.AwContents;
21 import org.chromium.android_webview.AwSettings;
22 import org.chromium.android_webview.test.TestAwContentsClient.OnDownloadStartHelper;
23 import org.chromium.android_webview.test.util.CommonResources;
24 import org.chromium.base.test.util.Feature;
25 import org.chromium.content.browser.test.util.CallbackHelper;
26 import org.chromium.net.test.util.TestWebServer;
28 import java.io.InputStream;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
34 import java.util.concurrent.Callable;
35 import java.util.concurrent.Semaphore;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.atomic.AtomicInteger;
42 public class AwContentsTest extends AwTestBase {
44 private TestAwContentsClient mContentsClient = new TestAwContentsClient();
47 @Feature({"AndroidWebView"})
49 public void testCreateDestroy() throws Throwable {
50 // NOTE this test runs on UI thread, so we cannot call any async methods.
51 createAwTestContainerView(mContentsClient).getAwContents().destroy();
55 @Feature({"AndroidWebView"})
56 public void testCreateLoadPageDestroy() throws Throwable {
57 AwTestContainerView awTestContainerView =
58 createAwTestContainerViewOnMainSync(mContentsClient);
59 loadUrlSync(awTestContainerView.getAwContents(),
60 mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML);
61 destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
62 // It should be safe to call destroy multiple times.
63 destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
67 @Feature({"AndroidWebView"})
68 public void testCreateLoadDestroyManyTimes() throws Throwable {
69 final int CREATE_AND_DESTROY_REPEAT_COUNT = 10;
70 for (int i = 0; i < CREATE_AND_DESTROY_REPEAT_COUNT; ++i) {
71 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
72 AwContents awContents = testView.getAwContents();
74 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
75 destroyAwContentsOnMainSync(awContents);
80 @Feature({"AndroidWebView"})
81 public void testCreateLoadDestroyManyAtOnce() throws Throwable {
82 final int CREATE_AND_DESTROY_REPEAT_COUNT = 10;
83 AwTestContainerView views[] = new AwTestContainerView[CREATE_AND_DESTROY_REPEAT_COUNT];
85 for (int i = 0; i < views.length; ++i) {
86 views[i] = createAwTestContainerViewOnMainSync(mContentsClient);
87 loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(),
91 for (int i = 0; i < views.length; ++i) {
92 destroyAwContentsOnMainSync(views[i].getAwContents());
98 @Feature({"AndroidWebView"})
99 public void testCreateAndGcManyTimes() throws Throwable {
100 final int CONCURRENT_INSTANCES = 4;
101 final int REPETITIONS = 16;
102 // The system retains a strong ref to the last focused view (in InputMethodManager)
103 // so allow for 1 'leaked' instance.
104 final int MAX_IDLE_INSTANCES = 1;
108 pollOnUiThread(new Callable<Boolean>() {
110 public Boolean call() {
111 return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES;
114 for (int i = 0; i < REPETITIONS; ++i) {
115 for (int j = 0; j < CONCURRENT_INSTANCES; ++j) {
116 AwTestContainerView view = createAwTestContainerViewOnMainSync(mContentsClient);
117 loadUrlAsync(view.getAwContents(), "about:blank");
119 assertTrue(AwContents.getNativeInstanceCount() >= CONCURRENT_INSTANCES);
120 assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * CONCURRENT_INSTANCES);
121 runTestOnUiThread(new Runnable() {
124 getActivity().removeAllViews();
131 pollOnUiThread(new Callable<Boolean>() {
133 public Boolean call() {
134 return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES;
140 @Feature({"AndroidWebView"})
141 public void testUseAwSettingsAfterDestroy() throws Throwable {
142 AwTestContainerView awTestContainerView =
143 createAwTestContainerViewOnMainSync(mContentsClient);
144 AwSettings awSettings = getAwSettingsOnUiThread(awTestContainerView.getAwContents());
145 loadUrlSync(awTestContainerView.getAwContents(),
146 mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML);
147 destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
149 // AwSettings should still be usable even after native side is destroyed.
150 String newFontFamily = "serif";
151 awSettings.setStandardFontFamily(newFontFamily);
152 assertEquals(newFontFamily, awSettings.getStandardFontFamily());
153 boolean newBlockNetworkLoads = !awSettings.getBlockNetworkLoads();
154 awSettings.setBlockNetworkLoads(newBlockNetworkLoads);
155 assertEquals(newBlockNetworkLoads, awSettings.getBlockNetworkLoads());
158 private int callDocumentHasImagesSync(final AwContents awContents)
159 throws Throwable, InterruptedException {
160 // Set up a container to hold the result object and a semaphore to
161 // make the test wait for the result.
162 final AtomicInteger val = new AtomicInteger();
163 final Semaphore s = new Semaphore(0);
164 final Message msg = Message.obtain(new Handler(Looper.getMainLooper()) {
166 public void handleMessage(Message msg) {
171 runTestOnUiThread(new Runnable() {
174 awContents.documentHasImages(msg);
177 assertTrue(s.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
178 int result = val.get();
183 @Feature({"AndroidWebView"})
184 public void testDocumentHasImages() throws Throwable {
185 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
186 AwContents awContents = testView.getAwContents();
188 final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper();
190 final String mime = "text/html";
191 final String emptyDoc = "<head/><body/>";
192 final String imageDoc = "<head/><body><img/><img/></body>";
194 // Make sure a document that does not have images returns 0
195 loadDataSync(awContents, loadHelper, emptyDoc, mime, false);
196 int result = callDocumentHasImagesSync(awContents);
197 assertEquals(0, result);
199 // Make sure a document that does have images returns 1
200 loadDataSync(awContents, loadHelper, imageDoc, mime, false);
201 result = callDocumentHasImagesSync(awContents);
202 assertEquals(1, result);
206 @Feature({"AndroidWebView"})
207 public void testClearCacheMemoryAndDisk() throws Throwable {
208 final AwTestContainerView testContainer =
209 createAwTestContainerViewOnMainSync(mContentsClient);
210 final AwContents awContents = testContainer.getAwContents();
212 TestWebServer webServer = null;
214 webServer = new TestWebServer(false);
215 final String pagePath = "/clear_cache_test.html";
216 List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
217 // Set Cache-Control headers to cache this request. One century should be long enough.
218 headers.add(Pair.create("Cache-Control", "max-age=3153600000"));
219 headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT"));
220 final String pageUrl = webServer.setResponse(
221 pagePath, "<html><body>foo</body></html>", headers);
223 // First load to populate cache.
224 clearCacheOnUiThread(awContents, true);
225 loadUrlSync(awContents,
226 mContentsClient.getOnPageFinishedHelper(),
228 assertEquals(1, webServer.getRequestCount(pagePath));
230 // Load about:blank so next load is not treated as reload by webkit and force
231 // revalidate with the server.
232 loadUrlSync(awContents,
233 mContentsClient.getOnPageFinishedHelper(),
236 // No clearCache call, so should be loaded from cache.
237 loadUrlSync(awContents,
238 mContentsClient.getOnPageFinishedHelper(),
240 assertEquals(1, webServer.getRequestCount(pagePath));
243 loadUrlSync(awContents,
244 mContentsClient.getOnPageFinishedHelper(),
247 // Clear cache, so should hit server again.
248 clearCacheOnUiThread(awContents, true);
249 loadUrlSync(awContents,
250 mContentsClient.getOnPageFinishedHelper(),
252 assertEquals(2, webServer.getRequestCount(pagePath));
254 if (webServer != null) webServer.shutdown();
259 @Feature({"AndroidWebView"})
260 public void testClearCacheInQuickSuccession() throws Throwable {
261 final AwTestContainerView testContainer =
262 createAwTestContainerViewOnMainSync(new TestAwContentsClient());
263 final AwContents awContents = testContainer.getAwContents();
265 runTestOnUiThread(new Runnable() {
268 for (int i = 0; i < 10; ++i) {
269 awContents.clearCache(true);
276 @Feature({"AndroidWebView"})
277 public void testGetFavicon() throws Throwable {
278 AwContents.setShouldDownloadFavicons();
279 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
280 final AwContents awContents = testView.getAwContents();
282 TestWebServer webServer = null;
284 webServer = new TestWebServer(false);
286 final String faviconUrl = webServer.setResponseBase64(
287 "/" + CommonResources.FAVICON_FILENAME, CommonResources.FAVICON_DATA_BASE64,
288 CommonResources.getImagePngHeaders(false));
289 final String pageUrl = webServer.setResponse("/favicon.html",
290 CommonResources.FAVICON_STATIC_HTML, null);
292 // The getFavicon will return the right icon a certain time after
293 // the page load completes which makes it slightly hard to test.
294 final Bitmap defaultFavicon = awContents.getFavicon();
296 getAwSettingsOnUiThread(awContents).setImagesEnabled(true);
297 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
299 pollOnUiThread(new Callable<Boolean>() {
301 public Boolean call() {
302 return awContents.getFavicon() != null &&
303 !awContents.getFavicon().sameAs(defaultFavicon);
307 final Object originalFaviconSource = (new URL(faviconUrl)).getContent();
308 final Bitmap originalFavicon =
309 BitmapFactory.decodeStream((InputStream) originalFaviconSource);
310 assertNotNull(originalFavicon);
312 assertTrue(awContents.getFavicon().sameAs(originalFavicon));
315 if (webServer != null) webServer.shutdown();
319 @Feature({"AndroidWebView", "Downloads"})
321 public void testDownload() throws Throwable {
322 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
323 AwContents awContents = testView.getAwContents();
325 final String data = "download data";
326 final String contentDisposition = "attachment;filename=\"download.txt\"";
327 final String mimeType = "text/plain";
329 List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
330 downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
331 downloadHeaders.add(Pair.create("Content-Type", mimeType));
332 downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
334 TestWebServer webServer = null;
336 webServer = new TestWebServer(false);
337 final String pageUrl = webServer.setResponse(
338 "/download.txt", data, downloadHeaders);
339 final OnDownloadStartHelper downloadStartHelper =
340 mContentsClient.getOnDownloadStartHelper();
341 final int callCount = downloadStartHelper.getCallCount();
342 loadUrlAsync(awContents, pageUrl);
343 downloadStartHelper.waitForCallback(callCount);
345 assertEquals(pageUrl, downloadStartHelper.getUrl());
346 assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
347 assertEquals(mimeType, downloadStartHelper.getMimeType());
348 assertEquals(data.length(), downloadStartHelper.getContentLength());
350 if (webServer != null) webServer.shutdown();
354 @Feature({"AndroidWebView", "setNetworkAvailable"})
356 public void testSetNetworkAvailable() throws Throwable {
357 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
358 AwContents awContents = testView.getAwContents();
359 String SCRIPT = "navigator.onLine";
361 enableJavaScriptOnUiThread(awContents);
362 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
364 // Default to "online".
365 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
368 // Forcing "offline".
369 setNetworkAvailableOnUiThread(awContents, false);
370 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
374 setNetworkAvailableOnUiThread(awContents, true);
375 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
380 static class JavaScriptObject {
381 private CallbackHelper mCallbackHelper;
382 public JavaScriptObject(CallbackHelper callbackHelper) {
383 mCallbackHelper = callbackHelper;
387 mCallbackHelper.notifyCalled();
391 @Feature({"AndroidWebView", "JavaBridge"})
393 public void testJavaBridge() throws Throwable {
394 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
395 final CallbackHelper callback = new CallbackHelper();
397 runTestOnUiThread(new Runnable() {
400 AwContents awContents = testView.getAwContents();
401 AwSettings awSettings = awContents.getSettings();
402 awSettings.setJavaScriptEnabled(true);
403 awContents.addPossiblyUnsafeJavascriptInterface(
404 new JavaScriptObject(callback), "bridge", null);
405 awContents.evaluateJavaScriptEvenIfNotYetNavigated(
406 "javascript:window.bridge.run();");
409 callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
412 @Feature({"AndroidWebView"})
414 public void testEscapingOfErrorPage() throws Throwable {
415 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
416 AwContents awContents = testView.getAwContents();
417 String SCRIPT = "window.failed == true";
419 enableJavaScriptOnUiThread(awContents);
420 CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
421 int currentCallCount = onPageFinishedHelper.getCallCount();
422 loadUrlAsync(awContents,
423 "file:///file-that-does-not-exist#<script>window.failed = true;</script>");
424 // We must wait for two onPageFinished callbacks. One for the original failing URL, and
425 // one for the error page that we then display to the user.
426 onPageFinishedHelper.waitForCallback(currentCallCount, 2, WAIT_TIMEOUT_MS,
427 TimeUnit.MILLISECONDS);
429 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
433 @Feature({"AndroidWebView"})
435 public void testCanInjectHeaders() throws Throwable {
436 final AwTestContainerView testContainer =
437 createAwTestContainerViewOnMainSync(mContentsClient);
438 final AwContents awContents = testContainer.getAwContents();
440 TestWebServer webServer = null;
442 webServer = new TestWebServer(false);
443 final String pagePath = "/test_can_inject_headers.html";
444 final String pageUrl = webServer.setResponse(
445 pagePath, "<html><body>foo</body></html>", null);
447 final Map<String, String> extraHeaders = new HashMap<String, String>();
448 extraHeaders.put("Referer", "foo");
449 extraHeaders.put("X-foo", "bar");
450 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(),
451 webServer.getResponseUrl(pagePath), extraHeaders);
453 assertEquals(1, webServer.getRequestCount(pagePath));
455 HttpRequest request = webServer.getLastRequest(pagePath);
456 assertNotNull(request);
458 for (Map.Entry<String, String> value : extraHeaders.entrySet()) {
459 String header = value.getKey();
460 Header[] matchingHeaders = request.getHeaders(header);
461 assertEquals("header " + header + " not found", 1, matchingHeaders.length);
462 assertEquals(value.getValue(), matchingHeaders[0].getValue());
465 if (webServer != null) webServer.shutdown();