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