1 // Copyright (c) 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.chromium.android_webview.AwContents;
18 import org.chromium.android_webview.AwSettings;
19 import org.chromium.android_webview.test.util.CommonResources;
20 import org.chromium.base.test.util.Feature;
21 import org.chromium.content.browser.test.util.CallbackHelper;
22 import org.chromium.net.test.util.TestWebServer;
24 import java.io.InputStream;
26 import java.util.ArrayList;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.atomic.AtomicInteger;
29 import java.util.concurrent.Callable;
30 import java.util.concurrent.Semaphore;
31 import java.util.concurrent.TimeUnit;
32 import java.util.List;
37 public class AwContentsTest extends AwTestBase {
38 public static class OnDownloadStartHelper extends CallbackHelper {
41 String mContentDisposition;
45 public String getUrl() {
46 assert getCallCount() > 0;
50 public String getUserAgent() {
51 assert getCallCount() > 0;
55 public String getContentDisposition() {
56 assert getCallCount() > 0;
57 return mContentDisposition;
60 public String getMimeType() {
61 assert getCallCount() > 0;
65 public long getContentLength() {
66 assert getCallCount() > 0;
67 return mContentLength;
70 public void notifyCalled(String url, String userAgent, String contentDisposition,
71 String mimeType, long contentLength) {
73 mUserAgent = userAgent;
74 mContentDisposition = contentDisposition;
76 mContentLength = contentLength;
81 private static class TestAwContentsClient
82 extends org.chromium.android_webview.test.TestAwContentsClient {
84 private OnDownloadStartHelper mOnDownloadStartHelper;
86 public TestAwContentsClient() {
87 mOnDownloadStartHelper = new OnDownloadStartHelper();
90 public OnDownloadStartHelper getOnDownloadStartHelper() {
91 return mOnDownloadStartHelper;
95 public void onDownloadStart(String url,
97 String contentDisposition,
100 getOnDownloadStartHelper().notifyCalled(url, userAgent, contentDisposition, mimeType,
105 private TestAwContentsClient mContentsClient = new TestAwContentsClient();
108 @Feature({"AndroidWebView"})
110 public void testCreateDestroy() throws Throwable {
111 // NOTE this test runs on UI thread, so we cannot call any async methods.
112 createAwTestContainerView(mContentsClient).getAwContents().destroy();
116 @Feature({"AndroidWebView"})
117 public void testCreateLoadPageDestroy() throws Throwable {
118 AwTestContainerView awTestContainerView =
119 createAwTestContainerViewOnMainSync(mContentsClient);
120 loadUrlSync(awTestContainerView.getAwContents(),
121 mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML);
122 destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
123 // It should be safe to call destroy multiple times.
124 destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
128 @Feature({"AndroidWebView"})
129 public void testCreateLoadDestroyManyTimes() throws Throwable {
130 final int CREATE_AND_DESTROY_REPEAT_COUNT = 10;
131 for (int i = 0; i < CREATE_AND_DESTROY_REPEAT_COUNT; ++i) {
132 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
133 AwContents awContents = testView.getAwContents();
135 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
136 destroyAwContentsOnMainSync(awContents);
141 @Feature({"AndroidWebView"})
142 public void testCreateLoadDestroyManyAtOnce() throws Throwable {
143 final int CREATE_AND_DESTROY_REPEAT_COUNT = 10;
144 AwTestContainerView views[] = new AwTestContainerView[CREATE_AND_DESTROY_REPEAT_COUNT];
146 for (int i = 0; i < views.length; ++i) {
147 views[i] = createAwTestContainerViewOnMainSync(mContentsClient);
148 loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(),
152 for (int i = 0; i < views.length; ++i) {
153 destroyAwContentsOnMainSync(views[i].getAwContents());
158 public void testCreateAndGcManyTimes() throws Throwable {
159 final int CONCURRENT_INSTANCES = 4;
160 final int REPETITIONS = 16;
161 // The system retains a strong ref to the last focused view (in InputMethodManager)
162 // so allow for 1 'leaked' instance.
163 final int MAX_IDLE_INSTANCES = 1;
167 assertTrue(pollOnUiThread(new Callable<Boolean>() {
169 public Boolean call() {
170 return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES;
173 for (int i = 0; i < REPETITIONS; ++i) {
174 for (int j = 0; j < CONCURRENT_INSTANCES; ++j) {
175 AwTestContainerView view = createAwTestContainerViewOnMainSync(mContentsClient);
176 loadUrlAsync(view.getAwContents(), "about:blank");
178 assertTrue(AwContents.getNativeInstanceCount() >= CONCURRENT_INSTANCES);
179 assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * CONCURRENT_INSTANCES);
180 runTestOnUiThread(new Runnable() {
183 getActivity().removeAllViews();
190 assertTrue(pollOnUiThread(new Callable<Boolean>() {
192 public Boolean call() {
193 return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES;
198 private int callDocumentHasImagesSync(final AwContents awContents)
199 throws Throwable, InterruptedException {
200 // Set up a container to hold the result object and a semaphore to
201 // make the test wait for the result.
202 final AtomicInteger val = new AtomicInteger();
203 final Semaphore s = new Semaphore(0);
204 final Message msg = Message.obtain(new Handler(Looper.getMainLooper()) {
206 public void handleMessage(Message msg) {
211 runTestOnUiThread(new Runnable() {
214 awContents.documentHasImages(msg);
217 assertTrue(s.tryAcquire(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
218 int result = val.get();
223 @Feature({"AndroidWebView"})
224 public void testDocumentHasImages() throws Throwable {
225 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
226 AwContents awContents = testView.getAwContents();
228 final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper();
230 final String mime = "text/html";
231 final String emptyDoc = "<head/><body/>";
232 final String imageDoc = "<head/><body><img/><img/></body>";
234 // Make sure a document that does not have images returns 0
235 loadDataSync(awContents, loadHelper, emptyDoc, mime, false);
236 int result = callDocumentHasImagesSync(awContents);
237 assertEquals(0, result);
239 // Make sure a document that does have images returns 1
240 loadDataSync(awContents, loadHelper, imageDoc, mime, false);
241 result = callDocumentHasImagesSync(awContents);
242 assertEquals(1, result);
246 @Feature({"AndroidWebView"})
247 public void testClearCacheMemoryAndDisk() throws Throwable {
248 final AwTestContainerView testContainer =
249 createAwTestContainerViewOnMainSync(mContentsClient);
250 final AwContents awContents = testContainer.getAwContents();
252 TestWebServer webServer = null;
254 webServer = new TestWebServer(false);
255 final String pagePath = "/clear_cache_test.html";
256 List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
257 // Set Cache-Control headers to cache this request. One century should be long enough.
258 headers.add(Pair.create("Cache-Control", "max-age=3153600000"));
259 headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT"));
260 final String pageUrl = webServer.setResponse(
261 pagePath, "<html><body>foo</body></html>", headers);
263 // First load to populate cache.
264 clearCacheOnUiThread(awContents, true);
265 loadUrlSync(awContents,
266 mContentsClient.getOnPageFinishedHelper(),
268 assertEquals(1, webServer.getRequestCount(pagePath));
270 // Load about:blank so next load is not treated as reload by webkit and force
271 // revalidate with the server.
272 loadUrlSync(awContents,
273 mContentsClient.getOnPageFinishedHelper(),
276 // No clearCache call, so should be loaded from cache.
277 loadUrlSync(awContents,
278 mContentsClient.getOnPageFinishedHelper(),
280 assertEquals(1, webServer.getRequestCount(pagePath));
283 loadUrlSync(awContents,
284 mContentsClient.getOnPageFinishedHelper(),
287 // Clear cache, so should hit server again.
288 clearCacheOnUiThread(awContents, true);
289 loadUrlSync(awContents,
290 mContentsClient.getOnPageFinishedHelper(),
292 assertEquals(2, webServer.getRequestCount(pagePath));
294 if (webServer != null) webServer.shutdown();
299 @Feature({"AndroidWebView"})
300 public void testClearCacheInQuickSuccession() throws Throwable {
301 final AwTestContainerView testContainer =
302 createAwTestContainerViewOnMainSync(new TestAwContentsClient());
303 final AwContents awContents = testContainer.getAwContents();
305 runTestOnUiThread(new Runnable() {
308 for (int i = 0; i < 10; ++i) {
309 awContents.clearCache(true);
315 private static final long TEST_TIMEOUT = 20000L;
316 private static final int CHECK_INTERVAL = 100;
319 @Feature({"AndroidWebView"})
320 public void testGetFavicon() throws Throwable {
321 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
322 final AwContents awContents = testView.getAwContents();
324 TestWebServer webServer = null;
326 webServer = new TestWebServer(false);
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);
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();
338 getAwSettingsOnUiThread(awContents).setImagesEnabled(true);
339 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
341 assertTrue(pollOnUiThread(new Callable<Boolean>() {
343 public Boolean call() {
344 return awContents.getFavicon() != null &&
345 !awContents.getFavicon().sameAs(defaultFavicon);
349 final Object originalFaviconSource = (new URL(faviconUrl)).getContent();
350 final Bitmap originalFavicon =
351 BitmapFactory.decodeStream((InputStream)originalFaviconSource);
352 assertNotNull(originalFavicon);
354 assertTrue(awContents.getFavicon().sameAs(originalFavicon));
357 if (webServer != null) webServer.shutdown();
361 @Feature({"AndroidWebView", "Downloads"})
363 public void testDownload() throws Throwable {
364 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
365 AwContents awContents = testView.getAwContents();
367 final String data = "download data";
368 final String contentDisposition = "attachment;filename=\"download.txt\"";
369 final String mimeType = "text/plain";
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())));
376 TestWebServer webServer = null;
378 webServer = new TestWebServer(false);
379 final String pageUrl = webServer.setResponse(
380 "/download.txt", data, downloadHeaders);
381 final OnDownloadStartHelper downloadStartHelper =
382 mContentsClient.getOnDownloadStartHelper();
383 final int callCount = downloadStartHelper.getCallCount();
384 loadUrlAsync(awContents, pageUrl);
385 downloadStartHelper.waitForCallback(callCount);
387 assertEquals(pageUrl, downloadStartHelper.getUrl());
388 assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
389 assertEquals(mimeType, downloadStartHelper.getMimeType());
390 assertEquals(data.length(), downloadStartHelper.getContentLength());
392 if (webServer != null) webServer.shutdown();
396 @Feature({"AndroidWebView", "setNetworkAvailable"})
398 public void testSetNetworkAvailable() throws Throwable {
399 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
400 AwContents awContents = testView.getAwContents();
401 String SCRIPT = "navigator.onLine";
403 enableJavaScriptOnUiThread(awContents);
404 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
406 // Default to "online".
407 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
410 // Forcing "offline".
411 awContents.setNetworkAvailable(false);
412 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
416 awContents.setNetworkAvailable(true);
417 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
422 static class JavaScriptObject {
423 private CallbackHelper mCallbackHelper;
424 public JavaScriptObject(CallbackHelper callbackHelper) {
425 mCallbackHelper = callbackHelper;
429 mCallbackHelper.notifyCalled();
433 @Feature({"AndroidWebView", "JavaBridge"})
435 public void testJavaBridge() throws Throwable {
436 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
437 final CallbackHelper callback = new CallbackHelper();
439 runTestOnUiThread(new Runnable() {
442 AwContents awContents = testView.getAwContents();
443 AwSettings awSettings = awContents.getSettings();
444 awSettings.setJavaScriptEnabled(true);
445 awContents.addPossiblyUnsafeJavascriptInterface(
446 new JavaScriptObject(callback), "bridge", null);
447 awContents.evaluateJavaScriptEvenIfNotYetNavigated(
448 "javascript:window.bridge.run();");
451 callback.waitForCallback(0, 1, 20, TimeUnit.SECONDS);