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. |
4 | |
5 | package org.chromium.android_webview.test; |
6 | |
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; |
16 | |
17 | import org.chromium.android_webview.AwContents; |
18 | import org.chromium.android_webview.test.util.CommonResources; |
19 | import org.chromium.base.test.util.DisabledTest; |
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; |
23 | |
24 | import java.io.InputStream; |
25 | import java.net.URL; |
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; |
33 | |
34 | /** |
35 | * AwContents tests. |
36 | */ |
37 | public class AwContentsTest extends AwTestBase { |
38 | public static class OnDownloadStartHelper extends CallbackHelper { |
39 | String mUrl; |
40 | String mUserAgent; |
41 | String mContentDisposition; |
42 | String mMimeType; |
43 | long mContentLength; |
44 | |
45 | public String getUrl() { |
46 | assert getCallCount() > 0; |
47 | return mUrl; |
48 | } |
49 | |
50 | public String getUserAgent() { |
51 | assert getCallCount() > 0; |
52 | return mUserAgent; |
53 | } |
54 | |
55 | public String getContentDisposition() { |
56 | assert getCallCount() > 0; |
57 | return mContentDisposition; |
58 | } |
59 | |
60 | public String getMimeType() { |
61 | assert getCallCount() > 0; |
62 | return mMimeType; |
63 | } |
64 | |
65 | public long getContentLength() { |
66 | assert getCallCount() > 0; |
67 | return mContentLength; |
68 | } |
69 | |
70 | public void notifyCalled(String url, String userAgent, String contentDisposition, |
71 | String mimeType, long contentLength) { |
72 | mUrl = url; |
73 | mUserAgent = userAgent; |
74 | mContentDisposition = contentDisposition; |
75 | mMimeType = mimeType; |
76 | mContentLength = contentLength; |
77 | notifyCalled(); |
78 | } |
79 | } |
80 | |
81 | private static class TestAwContentsClient |
82 | extends org.chromium.android_webview.test.TestAwContentsClient { |
83 | |
84 | private OnDownloadStartHelper mOnDownloadStartHelper; |
85 | |
86 | public TestAwContentsClient() { |
87 | mOnDownloadStartHelper = new OnDownloadStartHelper(); |
88 | } |
89 | |
90 | public OnDownloadStartHelper getOnDownloadStartHelper() { |
91 | return mOnDownloadStartHelper; |
92 | } |
93 | |
94 | @Override |
95 | public void onDownloadStart(String url, |
96 | String userAgent, |
97 | String contentDisposition, |
98 | String mimeType, |
99 | long contentLength) { |
100 | getOnDownloadStartHelper().notifyCalled(url, userAgent, contentDisposition, mimeType, |
101 | contentLength); |
102 | } |
103 | } |
104 | |
105 | private TestAwContentsClient mContentsClient = new TestAwContentsClient(); |
106 | |
107 | @SmallTest |
108 | @Feature({"AndroidWebView"}) |
109 | @UiThreadTest |
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(); |
113 | } |
114 | |
115 | @SmallTest |
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()); |
125 | } |
126 | |
127 | @LargeTest |
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(); |
134 | |
135 | loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), "about:blank"); |
136 | destroyAwContentsOnMainSync(awContents); |
137 | } |
138 | } |
139 | |
140 | @LargeTest |
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]; |
145 | |
146 | for (int i = 0; i < views.length; ++i) { |
147 | views[i] = createAwTestContainerViewOnMainSync(mContentsClient); |
148 | loadUrlSync(views[i].getAwContents(), mContentsClient.getOnPageFinishedHelper(), |
149 | "about:blank"); |
150 | } |
151 | |
152 | for (int i = 0; i < views.length; ++i) { |
153 | destroyAwContentsOnMainSync(views[i].getAwContents()); |
154 | views[i] = null; |
155 | } |
156 | } |
157 | |
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; |
164 | |
165 | System.gc(); |
166 | |
167 | assertTrue(pollOnUiThread(new Callable<Boolean>() { |
168 | @Override |
169 | public Boolean call() { |
170 | return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES; |
171 | } |
172 | })); |
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"); |
177 | } |
178 | assertTrue(AwContents.getNativeInstanceCount() >= CONCURRENT_INSTANCES); |
179 | assertTrue(AwContents.getNativeInstanceCount() <= (i + 1) * CONCURRENT_INSTANCES); |
180 | runTestOnUiThread(new Runnable() { |
181 | @Override |
182 | public void run() { |
183 | getActivity().removeAllViews(); |
184 | } |
185 | }); |
186 | } |
187 | |
188 | System.gc(); |
189 | |
190 | assertTrue(pollOnUiThread(new Callable<Boolean>() { |
191 | @Override |
192 | public Boolean call() { |
193 | return AwContents.getNativeInstanceCount() <= MAX_IDLE_INSTANCES; |
194 | } |
195 | })); |
196 | } |
197 | |
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()) { |
205 | @Override |
206 | public void handleMessage(Message msg) { |
207 | val.set(msg.arg1); |
208 | s.release(); |
209 | } |
210 | }); |
211 | runTestOnUiThread(new Runnable() { |
212 | @Override |
213 | public void run() { |
214 | awContents.documentHasImages(msg); |
215 | } |
216 | }); |
217 | assertTrue(s.tryAcquire(WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)); |
218 | int result = val.get(); |
219 | return result; |
220 | } |
221 | |
222 | @SmallTest |
223 | @Feature({"AndroidWebView"}) |
224 | public void testDocumentHasImages() throws Throwable { |
225 | AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
226 | AwContents awContents = testView.getAwContents(); |
227 | |
228 | final CallbackHelper loadHelper = mContentsClient.getOnPageFinishedHelper(); |
229 | |
230 | final String mime = "text/html"; |
231 | final String emptyDoc = "<head/><body/>"; |
232 | final String imageDoc = "<head/><body><img/><img/></body>"; |
233 | |
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); |
238 | |
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); |
243 | } |
244 | |
245 | @SmallTest |
246 | @Feature({"AndroidWebView"}) |
247 | public void testClearCacheMemoryAndDisk() throws Throwable { |
248 | final AwTestContainerView testContainer = |
249 | createAwTestContainerViewOnMainSync(mContentsClient); |
250 | final AwContents awContents = testContainer.getAwContents(); |
251 | |
252 | TestWebServer webServer = null; |
253 | try { |
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); |
262 | |
263 | // First load to populate cache. |
264 | clearCacheOnUiThread(awContents, true); |
265 | loadUrlSync(awContents, |
266 | mContentsClient.getOnPageFinishedHelper(), |
267 | pageUrl); |
268 | assertEquals(1, webServer.getRequestCount(pagePath)); |
269 | |
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(), |
274 | "about:blank"); |
275 | |
276 | // No clearCache call, so should be loaded from cache. |
277 | loadUrlSync(awContents, |
278 | mContentsClient.getOnPageFinishedHelper(), |
279 | pageUrl); |
280 | assertEquals(1, webServer.getRequestCount(pagePath)); |
281 | |
282 | // Same as above. |
283 | loadUrlSync(awContents, |
284 | mContentsClient.getOnPageFinishedHelper(), |
285 | "about:blank"); |
286 | |
287 | // Clear cache, so should hit server again. |
288 | clearCacheOnUiThread(awContents, true); |
289 | loadUrlSync(awContents, |
290 | mContentsClient.getOnPageFinishedHelper(), |
291 | pageUrl); |
292 | assertEquals(2, webServer.getRequestCount(pagePath)); |
293 | } finally { |
294 | if (webServer != null) webServer.shutdown(); |
295 | } |
296 | } |
297 | |
298 | @SmallTest |
299 | @Feature({"AndroidWebView"}) |
300 | public void testClearCacheInQuickSuccession() throws Throwable { |
301 | final AwTestContainerView testContainer = |
302 | createAwTestContainerViewOnMainSync(new TestAwContentsClient()); |
303 | final AwContents awContents = testContainer.getAwContents(); |
304 | |
305 | runTestOnUiThread(new Runnable() { |
306 | @Override |
307 | public void run() { |
308 | for (int i = 0; i < 10; ++i) { |
309 | awContents.clearCache(true); |
310 | } |
311 | } |
312 | }); |
313 | } |
314 | |
315 | private static final long TEST_TIMEOUT = 20000L; |
316 | private static final int CHECK_INTERVAL = 100; |
317 | |
318 | @SmallTest |
319 | @Feature({"AndroidWebView"}) |
320 | public void testGetFavicon() throws Throwable { |
321 | final AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
322 | final AwContents awContents = testView.getAwContents(); |
323 | |
324 | TestWebServer webServer = null; |
325 | try { |
326 | webServer = new TestWebServer(false); |
327 | |
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); |
333 | |
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(); |
337 | |
338 | getAwSettingsOnUiThread(awContents).setImagesEnabled(true); |
339 | loadUrlSync(awContents, mContentsClient.getOnPageFinishedHelper(), pageUrl); |
340 | |
341 | assertTrue(pollOnUiThread(new Callable<Boolean>() { |
342 | @Override |
343 | public Boolean call() { |
344 | return awContents.getFavicon() != null && |
345 | !awContents.getFavicon().sameAs(defaultFavicon); |
346 | } |
347 | })); |
348 | |
349 | final Object originalFaviconSource = (new URL(faviconUrl)).getContent(); |
350 | final Bitmap originalFavicon = |
351 | BitmapFactory.decodeStream((InputStream)originalFaviconSource); |
352 | assertNotNull(originalFavicon); |
353 | |
354 | assertTrue(awContents.getFavicon().sameAs(originalFavicon)); |
355 | |
356 | } finally { |
357 | if (webServer != null) webServer.shutdown(); |
358 | } |
359 | } |
360 | |
361 | @Feature({"AndroidWebView", "Downloads"}) |
362 | @SmallTest |
363 | public void testDownload() throws Throwable { |
364 | AwTestContainerView testView = createAwTestContainerViewOnMainSync(mContentsClient); |
365 | AwContents awContents = testView.getAwContents(); |
366 | |
367 | final String data = "download data"; |
368 | final String contentDisposition = "attachment;filename=\"download.txt\""; |
369 | final String mimeType = "text/plain"; |
370 | |
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()))); |
375 | |
376 | TestWebServer webServer = null; |
377 | try { |
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); |
386 | |
387 | assertEquals(pageUrl, downloadStartHelper.getUrl()); |
388 | assertEquals(contentDisposition, downloadStartHelper.getContentDisposition()); |
389 | assertEquals(mimeType, downloadStartHelper.getMimeType()); |
390 | assertEquals(data.length(), downloadStartHelper.getContentLength()); |
391 | } finally { |
392 | if (webServer != null) webServer.shutdown(); |
393 | } |
394 | } |
395 | } |