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.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;
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;
31 import java.io.InputStream;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
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;
45 public class AwContentsTest extends AwTestBase {
47 private TestAwContentsClient mContentsClient = new TestAwContentsClient();
50 @Feature({"AndroidWebView"})
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();
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());
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();
76 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
77 destroyAwContentsOnMainSync(awContents);
82 @Feature({"AndroidWebView"})
83 public void testCreateLoadDestroyManyAtOnce() throws Throwable {
84 AwTestContainerView views[] = new AwTestContainerView[10];
86 for (int i = 0; i < views.length; ++i) {
87 views[i] = createAwTestContainerViewOnMainSync(mContentsClient);
88 loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(),
92 for (int i = 0; i < views.length; ++i) {
93 destroyAwContentsOnMainSync(views[i].getAwContents());
99 @Feature({"AndroidWebView"})
101 public void testWebViewApisFailGracefullyAfterDestruction() throws Throwable {
102 AwContents awContents =
103 createAwTestContainerView(mContentsClient).getAwContents();
104 awContents.destroy();
106 assertNull(awContents.getWebContents());
107 assertNull(awContents.getContentViewCore());
108 assertNull(awContents.getNavigationController());
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));
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;
153 pollOnUiThread(new Callable<Boolean>() {
155 public Boolean call() {
156 return AwContents.getNativeInstanceCount() <= maxIdleInstances;
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");
164 assertTrue(AwContents.getNativeInstanceCount() >= concurrentInstances);
165 assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * concurrentInstances);
166 runTestOnUiThread(new Runnable() {
169 getActivity().removeAllViews();
176 pollOnUiThread(new Callable<Boolean>() {
178 public Boolean call() {
179 return AwContents.getNativeInstanceCount() <= maxIdleInstances;
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());
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());
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()) {
211 public void handleMessage(Message msg) {
216 runTestOnUiThread(new Runnable() {
219 awContents.documentHasImages(msg);
222 assertTrue(s.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
223 int result = val.get();
228 @Feature({"AndroidWebView"})
229 public void testDocumentHasImages() throws Throwable {
230 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
231 AwContents awContents = testView.getAwContents();
233 final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper();
235 final String mime = "text/html";
236 final String emptyDoc = "<head/><body/>";
237 final String imageDoc = "<head/><body><img/><img/></body>";
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);
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);
251 @Feature({"AndroidWebView"})
252 public void testClearCacheMemoryAndDisk() throws Throwable {
253 final AwTestContainerView testContainer =
254 createAwTestContainerViewOnMainSync(mContentsClient);
255 final AwContents awContents = testContainer.getAwContents();
257 TestWebServer webServer = TestWebServer.start();
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);
267 // First load to populate cache.
268 clearCacheOnUiThread(awContents, true);
269 loadUrlSync(awContents,
270 mContentsClient.getOnPageFinishedHelper(),
272 assertEquals(1, webServer.getRequestCount(pagePath));
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(),
280 // No clearCache call, so should be loaded from cache.
281 loadUrlSync(awContents,
282 mContentsClient.getOnPageFinishedHelper(),
284 assertEquals(1, webServer.getRequestCount(pagePath));
287 loadUrlSync(awContents,
288 mContentsClient.getOnPageFinishedHelper(),
291 // Clear cache, so should hit server again.
292 clearCacheOnUiThread(awContents, true);
293 loadUrlSync(awContents,
294 mContentsClient.getOnPageFinishedHelper(),
296 assertEquals(2, webServer.getRequestCount(pagePath));
298 webServer.shutdown();
303 @Feature({"AndroidWebView"})
304 public void testClearCacheInQuickSuccession() throws Throwable {
305 final AwTestContainerView testContainer =
306 createAwTestContainerViewOnMainSync(new TestAwContentsClient());
307 final AwContents awContents = testContainer.getAwContents();
309 runTestOnUiThread(new Runnable() {
312 for (int i = 0; i < 10; ++i) {
313 awContents.clearCache(true);
320 @Feature({"AndroidWebView"})
321 public void testGetFavicon() throws Throwable {
322 AwContents.setShouldDownloadFavicons();
323 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
324 final AwContents awContents = testView.getAwContents();
326 TestWebServer webServer = TestWebServer.start();
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 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 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 = TestWebServer.start();
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);
386 assertEquals(pageUrl, downloadStartHelper.getUrl());
387 assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
388 assertEquals(mimeType, downloadStartHelper.getMimeType());
389 assertEquals(data.length(), downloadStartHelper.getContentLength());
391 webServer.shutdown();
395 @Feature({"AndroidWebView", "setNetworkAvailable"})
397 public void testSetNetworkAvailable() throws Throwable {
398 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
399 AwContents awContents = testView.getAwContents();
400 String script = "navigator.onLine";
402 enableJavaScriptOnUiThread(awContents);
403 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
405 // Default to "online".
406 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
409 // Forcing "offline".
410 setNetworkAvailableOnUiThread(awContents, false);
411 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
415 setNetworkAvailableOnUiThread(awContents, true);
416 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
421 static class JavaScriptObject {
422 private CallbackHelper mCallbackHelper;
423 public JavaScriptObject(CallbackHelper callbackHelper) {
424 mCallbackHelper = callbackHelper;
428 mCallbackHelper.notifyCalled();
432 @Feature({"AndroidWebView", "JavaBridge"})
434 public void testJavaBridge() throws Throwable {
435 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
436 final CallbackHelper callback = new CallbackHelper();
438 runTestOnUiThread(new Runnable() {
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);
449 callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
452 @Feature({"AndroidWebView"})
454 public void testEscapingOfErrorPage() throws Throwable {
455 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
456 AwContents awContents = testView.getAwContents();
457 String script = "window.failed == true";
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);
467 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
471 @Feature({"AndroidWebView"})
473 public void testCanInjectHeaders() throws Throwable {
474 final AwTestContainerView testContainer =
475 createAwTestContainerViewOnMainSync(mContentsClient);
476 final AwContents awContents = testContainer.getAwContents();
478 TestWebServer webServer = TestWebServer.start();
480 final String pagePath = "/test_can_inject_headers.html";
481 final String pageUrl = webServer.setResponse(
482 pagePath, "<html><body>foo</body></html>", null);
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);
490 assertEquals(1, webServer.getRequestCount(pagePath));
492 HttpRequest request = webServer.getLastRequest(pagePath);
493 assertNotNull(request);
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());
502 webServer.shutdown();