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 = null;
259 webServer = new TestWebServer(false);
260 final String pagePath = "/clear_cache_test.html";
261 List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
262 // Set Cache-Control headers to cache this request. One century should be long enough.
263 headers.add(Pair.create("Cache-Control", "max-age=3153600000"));
264 headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT"));
265 final String pageUrl = webServer.setResponse(
266 pagePath, "<html><body>foo</body></html>", headers);
268 // First load to populate cache.
269 clearCacheOnUiThread(awContents, true);
270 loadUrlSync(awContents,
271 mContentsClient.getOnPageFinishedHelper(),
273 assertEquals(1, webServer.getRequestCount(pagePath));
275 // Load about:blank so next load is not treated as reload by webkit and force
276 // revalidate with the server.
277 loadUrlSync(awContents,
278 mContentsClient.getOnPageFinishedHelper(),
281 // No clearCache call, so should be loaded from cache.
282 loadUrlSync(awContents,
283 mContentsClient.getOnPageFinishedHelper(),
285 assertEquals(1, webServer.getRequestCount(pagePath));
288 loadUrlSync(awContents,
289 mContentsClient.getOnPageFinishedHelper(),
292 // Clear cache, so should hit server again.
293 clearCacheOnUiThread(awContents, true);
294 loadUrlSync(awContents,
295 mContentsClient.getOnPageFinishedHelper(),
297 assertEquals(2, webServer.getRequestCount(pagePath));
299 if (webServer != null) webServer.shutdown();
304 @Feature({"AndroidWebView"})
305 public void testClearCacheInQuickSuccession() throws Throwable {
306 final AwTestContainerView testContainer =
307 createAwTestContainerViewOnMainSync(new TestAwContentsClient());
308 final AwContents awContents = testContainer.getAwContents();
310 runTestOnUiThread(new Runnable() {
313 for (int i = 0; i < 10; ++i) {
314 awContents.clearCache(true);
321 @Feature({"AndroidWebView"})
322 public void testGetFavicon() throws Throwable {
323 AwContents.setShouldDownloadFavicons();
324 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
325 final AwContents awContents = testView.getAwContents();
327 TestWebServer webServer = null;
329 webServer = new TestWebServer(false);
331 final String faviconUrl = webServer.setResponseBase64(
332 "/" + CommonResources.FAVICON_FILENAME, CommonResources.FAVICON_DATA_BASE64,
333 CommonResources.getImagePngHeaders(false));
334 final String pageUrl = webServer.setResponse("/favicon.html",
335 CommonResources.FAVICON_STATIC_HTML, null);
337 // The getFavicon will return the right icon a certain time after
338 // the page load completes which makes it slightly hard to test.
339 final Bitmap defaultFavicon = awContents.getFavicon();
341 getAwSettingsOnUiThread(awContents).setImagesEnabled(true);
342 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
344 pollOnUiThread(new Callable<Boolean>() {
346 public Boolean call() {
347 return awContents.getFavicon() != null &&
348 !awContents.getFavicon().sameAs(defaultFavicon);
352 final Object originalFaviconSource = (new URL(faviconUrl)).getContent();
353 final Bitmap originalFavicon =
354 BitmapFactory.decodeStream((InputStream) originalFaviconSource);
355 assertNotNull(originalFavicon);
357 assertTrue(awContents.getFavicon().sameAs(originalFavicon));
360 if (webServer != null) webServer.shutdown();
364 @Feature({"AndroidWebView", "Downloads"})
366 public void testDownload() throws Throwable {
367 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
368 AwContents awContents = testView.getAwContents();
370 final String data = "download data";
371 final String contentDisposition = "attachment;filename=\"download.txt\"";
372 final String mimeType = "text/plain";
374 List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
375 downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
376 downloadHeaders.add(Pair.create("Content-Type", mimeType));
377 downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
379 TestWebServer webServer = null;
381 webServer = new TestWebServer(false);
382 final String pageUrl = webServer.setResponse(
383 "/download.txt", data, downloadHeaders);
384 final OnDownloadStartHelper downloadStartHelper =
385 mContentsClient.getOnDownloadStartHelper();
386 final int callCount = downloadStartHelper.getCallCount();
387 loadUrlAsync(awContents, pageUrl);
388 downloadStartHelper.waitForCallback(callCount);
390 assertEquals(pageUrl, downloadStartHelper.getUrl());
391 assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
392 assertEquals(mimeType, downloadStartHelper.getMimeType());
393 assertEquals(data.length(), downloadStartHelper.getContentLength());
395 if (webServer != null) webServer.shutdown();
399 @Feature({"AndroidWebView", "setNetworkAvailable"})
401 public void testSetNetworkAvailable() throws Throwable {
402 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
403 AwContents awContents = testView.getAwContents();
404 String script = "navigator.onLine";
406 enableJavaScriptOnUiThread(awContents);
407 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
409 // Default to "online".
410 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
413 // Forcing "offline".
414 setNetworkAvailableOnUiThread(awContents, false);
415 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
419 setNetworkAvailableOnUiThread(awContents, true);
420 assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
425 static class JavaScriptObject {
426 private CallbackHelper mCallbackHelper;
427 public JavaScriptObject(CallbackHelper callbackHelper) {
428 mCallbackHelper = callbackHelper;
432 mCallbackHelper.notifyCalled();
436 @Feature({"AndroidWebView", "JavaBridge"})
438 public void testJavaBridge() throws Throwable {
439 final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
440 final CallbackHelper callback = new CallbackHelper();
442 runTestOnUiThread(new Runnable() {
445 AwContents awContents = testView.getAwContents();
446 AwSettings awSettings = awContents.getSettings();
447 awSettings.setJavaScriptEnabled(true);
448 awContents.addPossiblyUnsafeJavascriptInterface(
449 new JavaScriptObject(callback), "bridge", null);
450 awContents.evaluateJavaScript("javascript:window.bridge.run();", null);
453 callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
456 @Feature({"AndroidWebView"})
458 public void testEscapingOfErrorPage() throws Throwable {
459 AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
460 AwContents awContents = testView.getAwContents();
461 String script = "window.failed == true";
463 enableJavaScriptOnUiThread(awContents);
464 CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
465 int currentCallCount = onPageFinishedHelper.getCallCount();
466 loadUrlAsync(awContents,
467 "file:///file-that-does-not-exist#<script>window.failed = true;</script>");
468 onPageFinishedHelper.waitForCallback(currentCallCount, 1, WAIT_TIMEOUT_MS,
469 TimeUnit.MILLISECONDS);
471 assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
475 @Feature({"AndroidWebView"})
477 public void testCanInjectHeaders() throws Throwable {
478 final AwTestContainerView testContainer =
479 createAwTestContainerViewOnMainSync(mContentsClient);
480 final AwContents awContents = testContainer.getAwContents();
482 TestWebServer webServer = null;
484 webServer = new TestWebServer(false);
485 final String pagePath = "/test_can_inject_headers.html";
486 final String pageUrl = webServer.setResponse(
487 pagePath, "<html><body>foo</body></html>", null);
489 final Map<String, String> extraHeaders = new HashMap<String, String>();
490 extraHeaders.put("Referer", "foo");
491 extraHeaders.put("X-foo", "bar");
492 loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(),
493 webServer.getResponseUrl(pagePath), extraHeaders);
495 assertEquals(1, webServer.getRequestCount(pagePath));
497 HttpRequest request = webServer.getLastRequest(pagePath);
498 assertNotNull(request);
500 for (Map.Entry<String, String> value : extraHeaders.entrySet()) {
501 String header = value.getKey();
502 Header[] matchingHeaders = request.getHeaders(header);
503 assertEquals("header " + header + " not found", 1, matchingHeaders.length);
504 assertEquals(value.getValue(), matchingHeaders[0].getValue());
507 if (webServer != null) webServer.shutdown();