AwContentsTest.java revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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.
4
5package org.chromium.android_webview.test;
6
7import android.graphics.Bitmap;
8import android.graphics.BitmapFactory;
9import android.os.Handler;
10import android.os.Looper;
11import android.os.Message;
12import android.test.UiThreadTest;
13import android.test.suitebuilder.annotation.LargeTest;
14import android.test.suitebuilder.annotation.SmallTest;
15import android.util.Pair;
16
17import org.chromium.android_webview.AwContents;
18import org.chromium.android_webview.AwSettings;
19import org.chromium.android_webview.test.util.CommonResources;
20import org.chromium.base.test.util.Feature;
21import org.chromium.content.browser.test.util.CallbackHelper;
22import org.chromium.net.test.util.TestWebServer;
23
24import java.io.InputStream;
25import java.net.URL;
26import java.util.ArrayList;
27import java.util.List;
28import java.util.concurrent.Callable;
29import java.util.concurrent.Semaphore;
30import java.util.concurrent.TimeUnit;
31import java.util.concurrent.atomic.AtomicInteger;
32
33/**
34 * AwContents tests.
35 */
36public class AwContentsTest extends AwTestBase {
37    private static class OnDownloadStartHelper extends CallbackHelper {
38        String mUrl;
39        String mUserAgent;
40        String mContentDisposition;
41        String mMimeType;
42        long mContentLength;
43
44        public String getUrl() {
45            assert getCallCount() > 0;
46            return mUrl;
47        }
48
49        public String getUserAgent() {
50            assert getCallCount() > 0;
51            return mUserAgent;
52        }
53
54        public String getContentDisposition() {
55            assert getCallCount() > 0;
56            return mContentDisposition;
57        }
58
59       public String getMimeType() {
60            assert getCallCount() > 0;
61            return mMimeType;
62        }
63
64        public long getContentLength() {
65            assert getCallCount() > 0;
66            return mContentLength;
67        }
68
69        public void notifyCalled(String url, String userAgent, String contentDisposition,
70                String mimeType, long contentLength) {
71            mUrl = url;
72            mUserAgent = userAgent;
73            mContentDisposition = contentDisposition;
74            mMimeType = mimeType;
75            mContentLength = contentLength;
76            notifyCalled();
77        }
78    }
79
80    private static class TestAwContentsClient
81            extends org.chromium.android_webview.test.TestAwContentsClient {
82
83        private OnDownloadStartHelper mOnDownloadStartHelper;
84
85        public TestAwContentsClient() {
86            mOnDownloadStartHelper = new OnDownloadStartHelper();
87        }
88
89        public OnDownloadStartHelper getOnDownloadStartHelper() {
90            return mOnDownloadStartHelper;
91        }
92
93        @Override
94        public void onDownloadStart(String url,
95                                    String userAgent,
96                                    String contentDisposition,
97                                    String mimeType,
98                                    long contentLength) {
99            getOnDownloadStartHelper().notifyCalled(url, userAgent, contentDisposition, mimeType,
100                    contentLength);
101        }
102    }
103
104    private TestAwContentsClient mContentsClient = new TestAwContentsClient();
105
106    @SmallTest
107    @Feature({"AndroidWebView"})
108    @UiThreadTest
109    public void testCreateDestroy() throws Throwable {
110        // NOTE this test runs on UI thread, so we cannot call any async methods.
111        createAwTestContainerView(mContentsClient).getAwContents().destroy();
112    }
113
114    @SmallTest
115    @Feature({"AndroidWebView"})
116    public void testCreateLoadPageDestroy() throws Throwable {
117        AwTestContainerView awTestContainerView =
118                createAwTestContainerViewOnMainSync(mContentsClient);
119        loadUrlSync(awTestContainerView.getAwContents(),
120                mContentsClient.getOnPageFinishedHelper(), CommonResources.ABOUT_HTML);
121        destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
122        // It should be safe to call destroy multiple times.
123        destroyAwContentsOnMainSync(awTestContainerView.getAwContents());
124    }
125
126    @LargeTest
127    @Feature({"AndroidWebView"})
128    public void testCreateLoadDestroyManyTimes() throws Throwable {
129        final int CREATE_AND_DESTROY_REPEAT_COUNT = 10;
130        for (int i = 0; i < CREATE_AND_DESTROY_REPEAT_COUNT; ++i) {
131            AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
132            AwContents awContents = testView.getAwContents();
133
134            loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
135            destroyAwContentsOnMainSync(awContents);
136        }
137    }
138
139    @LargeTest
140    @Feature({"AndroidWebView"})
141    public void testCreateLoadDestroyManyAtOnce() throws Throwable {
142        final int CREATE_AND_DESTROY_REPEAT_COUNT = 10;
143        AwTestContainerView views[] = new AwTestContainerView[CREATE_AND_DESTROY_REPEAT_COUNT];
144
145        for (int i = 0; i < views.length; ++i) {
146            views[i] = createAwTestContainerViewOnMainSync(mContentsClient);
147            loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(),
148                    "about:blank");
149        }
150
151        for (int i = 0; i < views.length; ++i) {
152            destroyAwContentsOnMainSync(views[i].getAwContents());
153            views[i] = null;
154        }
155    }
156
157    public void testCreateAndGcManyTimes() throws Throwable {
158        final int CONCURRENT_INSTANCES = 4;
159        final int REPETITIONS = 16;
160        // The system retains a strong ref to the last focused view (in InputMethodManager)
161        // so allow for 1 'leaked' instance.
162        final int MAX_IDLE_INSTANCES = 1;
163
164        System.gc();
165
166        pollOnUiThread(new Callable<Boolean>() {
167            @Override
168            public Boolean call() {
169                return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES;
170            }
171        });
172        for (int i = 0; i < REPETITIONS; ++i) {
173            for (int j = 0; j < CONCURRENT_INSTANCES; ++j) {
174                AwTestContainerView view = createAwTestContainerViewOnMainSync(mContentsClient);
175                loadUrlAsync(view.getAwContents(), "about:blank");
176            }
177            assertTrue(AwContents.getNativeInstanceCount() >= CONCURRENT_INSTANCES);
178            assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * CONCURRENT_INSTANCES);
179            runTestOnUiThread(new Runnable() {
180                @Override
181                public void run() {
182                    getActivity().removeAllViews();
183                }
184            });
185        }
186
187        System.gc();
188
189        pollOnUiThread(new Callable<Boolean>() {
190            @Override
191            public Boolean call() {
192                return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES;
193            }
194        });
195    }
196
197    private int callDocumentHasImagesSync(final AwContents awContents)
198            throws Throwable, InterruptedException {
199        // Set up a container to hold the result object and a semaphore to
200        // make the test wait for the result.
201        final AtomicInteger val = new AtomicInteger();
202        final Semaphore s = new Semaphore(0);
203        final Message msg = Message.obtain(new Handler(Looper.getMainLooper()) {
204            @Override
205            public void handleMessage(Message msg) {
206                val.set(msg.arg1);
207                s.release();
208            }
209        });
210        runTestOnUiThread(new Runnable() {
211            @Override
212            public void run() {
213              awContents.documentHasImages(msg);
214            }
215        });
216        assertTrue(s.tryAcquire(WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
217        int result = val.get();
218        return result;
219    }
220
221    @SmallTest
222    @Feature({"AndroidWebView"})
223    public void testDocumentHasImages() throws Throwable {
224        AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
225        AwContents awContents = testView.getAwContents();
226
227        final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper();
228
229        final String mime = "text/html";
230        final String emptyDoc = "<head/><body/>";
231        final String imageDoc = "<head/><body><img/><img/></body>";
232
233        // Make sure a document that does not have images returns 0
234        loadDataSync(awContents, loadHelper, emptyDoc, mime, false);
235        int result = callDocumentHasImagesSync(awContents);
236        assertEquals(0, result);
237
238        // Make sure a document that does have images returns 1
239        loadDataSync(awContents, loadHelper, imageDoc, mime, false);
240        result = callDocumentHasImagesSync(awContents);
241        assertEquals(1, result);
242    }
243
244    @SmallTest
245    @Feature({"AndroidWebView"})
246    public void testClearCacheMemoryAndDisk() throws Throwable {
247        final AwTestContainerView testContainer =
248                createAwTestContainerViewOnMainSync(mContentsClient);
249        final AwContents awContents = testContainer.getAwContents();
250
251        TestWebServer webServer = null;
252        try {
253            webServer = new TestWebServer(false);
254            final String pagePath = "/clear_cache_test.html";
255            List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
256            // Set Cache-Control headers to cache this request. One century should be long enough.
257            headers.add(Pair.create("Cache-Control", "max-age=3153600000"));
258            headers.add(Pair.create("Last-Modified", "Wed, 3 Oct 2012 00:00:00 GMT"));
259            final String pageUrl = webServer.setResponse(
260                    pagePath, "<html><body>foo</body></html>", headers);
261
262            // First load to populate cache.
263            clearCacheOnUiThread(awContents, true);
264            loadUrlSync(awContents,
265                        mContentsClient.getOnPageFinishedHelper(),
266                        pageUrl);
267            assertEquals(1, webServer.getRequestCount(pagePath));
268
269            // Load about:blank so next load is not treated as reload by webkit and force
270            // revalidate with the server.
271            loadUrlSync(awContents,
272                        mContentsClient.getOnPageFinishedHelper(),
273                        "about:blank");
274
275            // No clearCache call, so should be loaded from cache.
276            loadUrlSync(awContents,
277                        mContentsClient.getOnPageFinishedHelper(),
278                        pageUrl);
279            assertEquals(1, webServer.getRequestCount(pagePath));
280
281            // Same as above.
282            loadUrlSync(awContents,
283                        mContentsClient.getOnPageFinishedHelper(),
284                        "about:blank");
285
286            // Clear cache, so should hit server again.
287            clearCacheOnUiThread(awContents, true);
288            loadUrlSync(awContents,
289                        mContentsClient.getOnPageFinishedHelper(),
290                        pageUrl);
291            assertEquals(2, webServer.getRequestCount(pagePath));
292        } finally {
293            if (webServer != null) webServer.shutdown();
294        }
295    }
296
297    @SmallTest
298    @Feature({"AndroidWebView"})
299    public void testClearCacheInQuickSuccession() throws Throwable {
300        final AwTestContainerView testContainer =
301                createAwTestContainerViewOnMainSync(new TestAwContentsClient());
302        final AwContents awContents = testContainer.getAwContents();
303
304        runTestOnUiThread(new Runnable() {
305            @Override
306            public void run() {
307                for (int i = 0; i < 10; ++i) {
308                    awContents.clearCache(true);
309                }
310            }
311        });
312    }
313
314    @SmallTest
315    @Feature({"AndroidWebView"})
316    public void testGetFavicon() throws Throwable {
317        AwContents.setShouldDownloadFavicons();
318        final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
319        final AwContents awContents = testView.getAwContents();
320
321        TestWebServer webServer = null;
322        try {
323            webServer = new TestWebServer(false);
324
325            final String faviconUrl = webServer.setResponseBase64(
326                    "/" + CommonResources.FAVICON_FILENAME, CommonResources.FAVICON_DATA_BASE64,
327                    CommonResources.getImagePngHeaders(false));
328            final String pageUrl = webServer.setResponse("/favicon.html",
329                    CommonResources.FAVICON_STATIC_HTML, null);
330
331            // The getFavicon will return the right icon a certain time after
332            // the page load completes which makes it slightly hard to test.
333            final Bitmap defaultFavicon = awContents.getFavicon();
334
335            getAwSettingsOnUiThread(awContents).setImagesEnabled(true);
336            loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
337
338            pollOnUiThread(new Callable<Boolean>() {
339                @Override
340                public Boolean call() {
341                    return awContents.getFavicon() != null &&
342                        !awContents.getFavicon().sameAs(defaultFavicon);
343                }
344            });
345
346            final Object originalFaviconSource = (new URL(faviconUrl)).getContent();
347            final Bitmap originalFavicon =
348                BitmapFactory.decodeStream((InputStream) originalFaviconSource);
349            assertNotNull(originalFavicon);
350
351            assertTrue(awContents.getFavicon().sameAs(originalFavicon));
352
353        } finally {
354            if (webServer != null) webServer.shutdown();
355        }
356    }
357
358    @Feature({"AndroidWebView", "Downloads"})
359    @SmallTest
360    public void testDownload() throws Throwable {
361        AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
362        AwContents awContents = testView.getAwContents();
363
364        final String data = "download data";
365        final String contentDisposition = "attachment;filename=\"download.txt\"";
366        final String mimeType = "text/plain";
367
368        List<Pair<String, String>> downloadHeaders = new ArrayList<Pair<String, String>>();
369        downloadHeaders.add(Pair.create("Content-Disposition", contentDisposition));
370        downloadHeaders.add(Pair.create("Content-Type", mimeType));
371        downloadHeaders.add(Pair.create("Content-Length", Integer.toString(data.length())));
372
373        TestWebServer webServer = null;
374        try {
375            webServer = new TestWebServer(false);
376            final String pageUrl = webServer.setResponse(
377                    "/download.txt", data, downloadHeaders);
378            final OnDownloadStartHelper downloadStartHelper =
379                mContentsClient.getOnDownloadStartHelper();
380            final int callCount = downloadStartHelper.getCallCount();
381            loadUrlAsync(awContents, pageUrl);
382            downloadStartHelper.waitForCallback(callCount);
383
384            assertEquals(pageUrl, downloadStartHelper.getUrl());
385            assertEquals(contentDisposition, downloadStartHelper.getContentDisposition());
386            assertEquals(mimeType, downloadStartHelper.getMimeType());
387            assertEquals(data.length(), downloadStartHelper.getContentLength());
388        } finally {
389            if (webServer != null) webServer.shutdown();
390        }
391    }
392
393    @Feature({"AndroidWebView", "setNetworkAvailable"})
394    @SmallTest
395    public void testSetNetworkAvailable() throws Throwable {
396        AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
397        AwContents awContents = testView.getAwContents();
398        String SCRIPT = "navigator.onLine";
399
400        enableJavaScriptOnUiThread(awContents);
401        loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank");
402
403        // Default to "online".
404        assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
405              SCRIPT));
406
407        // Forcing "offline".
408        setNetworkAvailableOnUiThread(awContents, false);
409        assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
410              SCRIPT));
411
412        // Forcing "online".
413        setNetworkAvailableOnUiThread(awContents, true);
414        assertEquals("true", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
415              SCRIPT));
416    }
417
418
419    static class JavaScriptObject {
420        private CallbackHelper mCallbackHelper;
421        public JavaScriptObject(CallbackHelper callbackHelper) {
422            mCallbackHelper = callbackHelper;
423        }
424
425        public void run() {
426            mCallbackHelper.notifyCalled();
427        }
428    }
429
430    @Feature({"AndroidWebView", "JavaBridge"})
431    @SmallTest
432    public void testJavaBridge() throws Throwable {
433        final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
434        final CallbackHelper callback = new CallbackHelper();
435
436        runTestOnUiThread(new Runnable() {
437            @Override
438            public void run() {
439                AwContents awContents = testView.getAwContents();
440                AwSettings awSettings = awContents.getSettings();
441                awSettings.setJavaScriptEnabled(true);
442                awContents.addPossiblyUnsafeJavascriptInterface(
443                        new JavaScriptObject(callback), "bridge", null);
444                awContents.evaluateJavaScriptEvenIfNotYetNavigated(
445                        "javascript:window.bridge.run();");
446            }
447        });
448        callback.waitForCallback(0, 1, WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
449    }
450
451    @Feature({"AndroidWebView"})
452    @SmallTest
453    public void testEscapingOfErrorPage() throws Throwable {
454        AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient);
455        AwContents awContents = testView.getAwContents();
456        String SCRIPT = "window.failed == true";
457
458        enableJavaScriptOnUiThread(awContents);
459        CallbackHelper onPageFinishedHelper = mContentsClient.getOnPageFinishedHelper();
460        int currentCallCount = onPageFinishedHelper.getCallCount();
461        loadUrlAsync(awContents,
462                "file:///file-that-does-not-exist#<script>window.failed = true;</script>");
463        // We must wait for two onPageFinished callbacks. One for the original failing URL, and
464        // one for the error page that we then display to the user.
465        onPageFinishedHelper.waitForCallback(currentCallCount, 2, WAIT_TIMEOUT_MS,
466                                             TimeUnit.MILLISECONDS);
467
468        assertEquals("false", executeJavaScriptAndWaitForResult(awContents, mContentsClient,
469                SCRIPT));
470    }
471
472}
473