| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.android_webview.test; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.test.UiThreadTest; |
| import android.test.suitebuilder.annotation.LargeTest; |
| import android.test.suitebuilder.annotation.SmallTest; |
| import android.util.Pair; |
| |
| import org.chromium.android_webview.AwContents; |
| import org.chromium.android_webview.AwSettings; |
| import org.chromium.android_webview.test.util.CommonResources; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.content.browser.test.util.CallbackHelper; |
| import org.chromium.net.test.util.TestWebServer; |
| |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * AwContents tests. |
| */ |
| public class AwContentsTest extends AwTestBase { |
| public static class OnDownloadStartHelper extends CallbackHelper { |
| String mUrl; |
| String mUserAgent; |
| String mContentDisposition; |
| String mMimeType; |
| long mContentLength; |
| |
| public String getUrl() { |
| assert getCallCount() > 0; |
| return mUrl; |
| } |
| |
| public String getUserAgent() { |
| assert getCallCount() > 0; |
| return mUserAgent; |
| } |
| |
| public String getContentDisposition() { |
| assert getCallCount() > 0; |
| return mContentDisposition; |
| } |
| |
| public String getMimeType() { |
| assert getCallCount() > 0; |
| return mMimeType; |
| } |
| |
| public long getContentLength() { |
| assert getCallCount() > 0; |
| return mContentLength; |
| } |
| |
| public void notifyCalled(String url, String userAgent, String contentDisposition, |
| String mimeType, long contentLength) { |
| mUrl = url; |
| mUserAgent = userAgent; |
| mContentDisposition = contentDisposition; |
| mMimeType = mimeType; |
| mContentLength = contentLength; |
| notifyCalled(); |
| } |
| } |
| |
| private static class TestAwContentsClient |
| extends org.chromium.android_webview.test.TestAwContentsClient { |
| |
| private OnDownloadStartHelper mOnDownloadStartHelper; |
| |
| public TestAwContentsClient() { |
| mOnDownloadStartHelper = new OnDownloadStartHelper(); |
| } |
| |
| public OnDownloadStartHelper getOnDownloadStartHelper() { |
| return mOnDownloadStartHelper; |
| } |
| |
| @Override |
| public void onDownloadStart(String url, |
| String userAgent, |
| String contentDisposition, |
| String mimeType, |
| long contentLength) { |
| getOnDownloadStartHelper().notifyCalled(url, userAgent, contentDisposition, mimeType, |
| contentLength); |
| } |
| } |
| |
| private TestAwContentsClient mContentsClient = new TestAwContentsClient(); |
| |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| @UiThreadTest |
| public void testCreateDestroy() throws Throwable { |
| // NOTE this test runs on UI thread, so we cannot call any async methods. |
| createAwTestContainerView(mContentsClient).getAwContents().destroy(); |
| } |
| |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testCreateLoadPageDestroy() throws Throwable { |
| AwTestContainerView awTestContainerView = |
| createAwTestContainerViewOnMainSync(mContentsClient); |
| loadUrlSync(awTestContainerView.getAwContents(), |
| mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML); |
| destroyAwContentsOnMainSync(awTestContainerView.getAwContents()); |
| // It should be safe to call destroy multiple times. |
| destroyAwContentsOnMainSync(awTestContainerView.getAwContents()); |
| } |
| |
| @LargeTest |
| @Feature({"AndroidWebView"}) |
| public void testCreateLoadDestroyManyTimes() throws Throwable { |
| final int CREATE_AND_DESTROY_REPEAT_COUNT = 10; |
| for (int i = 0; i < CREATE_AND_DESTROY_REPEAT_COUNT; ++i) { |
| AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| AwContents awContents = testView.getAwContents(); |
| |
| loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank"); |
| destroyAwContentsOnMainSync(awContents); |
| } |
| } |
| |
| @LargeTest |
| @Feature({"AndroidWebView"}) |
| public void testCreateLoadDestroyManyAtOnce() throws Throwable { |
| final int CREATE_AND_DESTROY_REPEAT_COUNT = 10; |
| AwTestContainerView views[] = new AwTestContainerView[CREATE_AND_DESTROY_REPEAT_COUNT]; |
| |
| for (int i = 0; i < views.length; ++i) { |
| views[i] = createAwTestContainerViewOnMainSync(mContentsClient); |
| loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(), |
| "about:blank"); |
| } |
| |
| for (int i = 0; i < views.length; ++i) { |
| destroyAwContentsOnMainSync(views[i].getAwContents()); |
| views[i] = null; |
| } |
| } |
| |
| public void testCreateAndGcManyTimes() throws Throwable { |
| final int CONCURRENT_INSTANCES = 4; |
| final int REPETITIONS = 16; |
| // The system retains a strong ref to the last focused view (in InputMethodManager) |
| // so allow for 1 'leaked' instance. |
| final int MAX_IDLE_INSTANCES = 1; |
| |
| System.gc(); |
| |
| assertTrue(pollOnUiThread(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES; |
| } |
| })); |
| for (int i = 0; i < REPETITIONS; ++i) { |
| for (int j = 0; j < CONCURRENT_INSTANCES; ++j) { |
| AwTestContainerView view = createAwTestContainerViewOnMainSync(mContentsClient); |
| loadUrlAsync(view.getAwContents(), "about:blank"); |
| } |
| assertTrue(AwContents.getNativeInstanceCount() >= CONCURRENT_INSTANCES); |
| assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * CONCURRENT_INSTANCES); |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| getActivity().removeAllViews(); |
| } |
| }); |
| } |
| |
| System.gc(); |
| |
| assertTrue(pollOnUiThread(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES; |
| } |
| })); |
| } |
| |
| private int callDocumentHasImagesSync(final AwContents awContents) |
| throws Throwable, InterruptedException { |
| // Set up a container to hold the result object and a semaphore to |
| // make the test wait for the result. |
| final AtomicInteger val = new AtomicInteger(); |
| final Semaphore s = new Semaphore(0); |
| final Message msg = Message.obtain(new Handler(Looper.getMainLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| val.set(msg.arg1); |
| s.release(); |
| } |
| }); |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| awContents.documentHasImages(msg); |
| } |
| }); |
| assertTrue(s.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| int result = val.get(); |
| return result; |
| } |
| |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testDocumentHasImages() throws Throwable { |
| AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| AwContents awContents = testView.getAwContents(); |
| |
| final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper(); |
| |
| final String mime = "text/html"; |
| final String emptyDoc = "<head/><body/>"; |
| final String imageDoc = "<head/><body><img/><img/></body>"; |
| |
| // Make sure a document that does not have images returns 0 |
| loadDataSync(awContents, loadHelper, emptyDoc, mime, false); |
| int result = callDocumentHasImagesSync(awContents); |
| assertEquals(0, result); |
| |
| // Make sure a document that does have images returns 1 |
| loadDataSync(awContents, loadHelper, imageDoc, mime, false); |
| result = callDocumentHasImagesSync(awContents); |
| assertEquals(1, result); |
| } |
| |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testClearCacheMemoryAndDisk() throws Throwable { |
| final AwTestContainerView testContainer = |
| createAwTestContainerViewOnMainSync(mContentsClient); |
| final AwContents awContents = testContainer.getAwContents(); |
| |
| TestWebServer webServer = null; |
| try { |
| webServer = new TestWebServer(false); |
| final String pagePath = "/clear_cache_test.html"; |
| List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>(); |
| // Set Cache-Control headers to cache this request. One century should be long enough. |
| headers.add(Pair.create("Cache-Control", "max-age=3153600000")); |
| headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT")); |
| final String pageUrl = webServer.setResponse( |
| pagePath, "<html><body>foo</body></html>", headers); |
| |
| // First load to populate cache. |
| clearCacheOnUiThread(awContents, true); |
| loadUrlSync(awContents, |
| mContentsClient.getOnPageFinishedHelper(), |
| pageUrl); |
| assertEquals(1, webServer.getRequestCount(pagePath)); |
| |
| // Load about:blank so next load is not treated as reload by webkit and force |
| // revalidate with the server. |
| loadUrlSync(awContents, |
| mContentsClient.getOnPageFinishedHelper(), |
| "about:blank"); |
| |
| // No clearCache call, so should be loaded from cache. |
| loadUrlSync(awContents, |
| mContentsClient.getOnPageFinishedHelper(), |
| pageUrl); |
| assertEquals(1, webServer.getRequestCount(pagePath)); |
| |
| // Same as above. |
| loadUrlSync(awContents, |
| mContentsClient.getOnPageFinishedHelper(), |
| "about:blank"); |
| |
| // Clear cache, so should hit server again. |
| clearCacheOnUiThread(awContents, true); |
| loadUrlSync(awContents, |
| mContentsClient.getOnPageFinishedHelper(), |
| pageUrl); |
| assertEquals(2, webServer.getRequestCount(pagePath)); |
| } finally { |
| if (webServer != null) webServer.shutdown(); |
| } |
| } |
| |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testClearCacheInQuickSuccession() throws Throwable { |
| final AwTestContainerView testContainer = |
| createAwTestContainerViewOnMainSync(new TestAwContentsClient()); |
| final AwContents awContents = testContainer.getAwContents(); |
| |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| for (int i = 0; i < 10; ++i) { |
| awContents.clearCache(true); |
| } |
| } |
| }); |
| } |
| |
| @SmallTest |
| @Feature({"AndroidWebView"}) |
| public void testGetFavicon() throws Throwable { |
| final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| final AwContents awContents = testView.getAwContents(); |
| |
| TestWebServer webServer = null; |
| try { |
| webServer = new TestWebServer(false); |
| |
| final String faviconUrl = webServer.setResponseBase64( |
| "/" + CommonResources.FAVICON_FILENAME, CommonResources.FAVICON_DATA_BASE64, |
| CommonResources.getImagePngHeaders(false)); |
| final String pageUrl = webServer.setResponse("/favicon.html", |
| CommonResources.FAVICON_STATIC_HTML, null); |
| |
| // The getFavicon will return the right icon a certain time after |
| // the page load completes which makes it slightly hard to test. |
| final Bitmap defaultFavicon = awContents.getFavicon(); |
| |
| getAwSettingsOnUiThread(awContents).setImagesEnabled(true); |
| loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl); |
| |
| assertTrue(pollOnUiThread(new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return awContents.getFavicon() != null && |
| !awContents.getFavicon().sameAs(defaultFavicon); |
| } |
| })); |
| |
| final Object originalFaviconSource = (new URL(faviconUrl)).getContent(); |
| final Bitmap originalFavicon = |
| BitmapFactory.decodeStream((InputStream) originalFaviconSource); |
| assertNotNull(originalFavicon); |
| |
| assertTrue(awContents.getFavicon().sameAs(originalFavicon)); |
| |
| } finally { |
| if (webServer != null) webServer.shutdown(); |
| } |
| } |
| |
| @Feature({"AndroidWebView", "Downloads"}) |
| @SmallTest |
| public void testDownload() throws Throwable { |
| AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| AwContents awContents = testView.getAwContents(); |
| |
| final String data = "download data"; |
| final String contentDisposition = "attachment;filename=\"download.txt\""; |
| final String mimeType = "text/plain"; |
| |
| List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>(); |
| downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition)); |
| downloadHeaders.add(Pair.create("Content-Type", mimeType)); |
| downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length()))); |
| |
| TestWebServer webServer = null; |
| try { |
| webServer = new TestWebServer(false); |
| final String pageUrl = webServer.setResponse( |
| "/download.txt", data, downloadHeaders); |
| final OnDownloadStartHelper downloadStartHelper = |
| mContentsClient.getOnDownloadStartHelper(); |
| final int callCount = downloadStartHelper.getCallCount(); |
| loadUrlAsync(awContents, pageUrl); |
| downloadStartHelper.waitForCallback(callCount); |
| |
| assertEquals(pageUrl, downloadStartHelper.getUrl()); |
| assertEquals(contentDisposition, downloadStartHelper.getContentDisposition()); |
| assertEquals(mimeType, downloadStartHelper.getMimeType()); |
| assertEquals(data.length(), downloadStartHelper.getContentLength()); |
| } finally { |
| if (webServer != null) webServer.shutdown(); |
| } |
| } |
| |
| @Feature({"AndroidWebView", "setNetworkAvailable"}) |
| @SmallTest |
| public void testSetNetworkAvailable() throws Throwable { |
| AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| AwContents awContents = testView.getAwContents(); |
| String SCRIPT = "navigator.onLine"; |
| |
| enableJavaScriptOnUiThread(awContents); |
| loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank"); |
| |
| // Default to "online". |
| assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient, |
| SCRIPT)); |
| |
| // Forcing "offline". |
| setNetworkAvailableOnUiThread(awContents, false); |
| assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient, |
| SCRIPT)); |
| |
| // Forcing "online". |
| setNetworkAvailableOnUiThread(awContents, true); |
| assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient, |
| SCRIPT)); |
| } |
| |
| |
| static class JavaScriptObject { |
| private CallbackHelper mCallbackHelper; |
| public JavaScriptObject(CallbackHelper callbackHelper) { |
| mCallbackHelper = callbackHelper; |
| } |
| |
| public void run() { |
| mCallbackHelper.notifyCalled(); |
| } |
| } |
| |
| @Feature({"AndroidWebView", "JavaBridge"}) |
| @SmallTest |
| public void testJavaBridge() throws Throwable { |
| final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| final CallbackHelper callback = new CallbackHelper(); |
| |
| runTestOnUiThread(new Runnable() { |
| @Override |
| public void run() { |
| AwContents awContents = testView.getAwContents(); |
| AwSettings awSettings = awContents.getSettings(); |
| awSettings.setJavaScriptEnabled(true); |
| awContents.addPossiblyUnsafeJavascriptInterface( |
| new JavaScriptObject(callback), "bridge", null); |
| awContents.evaluateJavaScriptEvenIfNotYetNavigated( |
| "javascript:window.bridge.run();"); |
| } |
| }); |
| callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS); |
| } |
| |
| @Feature({"AndroidWebView"}) |
| @SmallTest |
| public void testEscapingOfErrorPage() throws Throwable { |
| AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
| AwContents awContents = testView.getAwContents(); |
| String SCRIPT = "window.failed == true"; |
| |
| enableJavaScriptOnUiThread(awContents); |
| CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper(); |
| int currentCallCount = onPageFinishedHelper.getCallCount(); |
| loadUrlAsync(awContents, |
| "file:///file-that-does-not-exist#<script>window.failed = true;</script>"); |
| // We must wait for two onPageFinished callbacks. One for the original failing URL, and |
| // one for the error page that we then display to the user. |
| onPageFinishedHelper.waitForCallback(currentCallCount, 2, WAIT_TIMEOUT_MS, |
| TimeUnit.MILLISECONDS); |
| |
| assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient, |
| SCRIPT)); |
| } |
| |
| } |