1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.webkit.cts; 18 19 import static org.hamcrest.MatcherAssert.assertThat; 20 import static org.hamcrest.Matchers.greaterThan; 21 import static org.hamcrest.Matchers.lessThan; 22 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.ContextWrapper; 26 import android.graphics.Bitmap; 27 import android.graphics.Bitmap.Config; 28 import android.graphics.Canvas; 29 import android.graphics.Color; 30 import android.graphics.Picture; 31 import android.graphics.Rect; 32 import android.graphics.pdf.PdfRenderer; 33 import android.net.Uri; 34 import android.os.Bundle; 35 import android.os.CancellationSignal; 36 import android.os.Handler; 37 import android.os.LocaleList; 38 import android.os.Looper; 39 import android.os.Message; 40 import android.os.ParcelFileDescriptor; 41 import android.os.StrictMode; 42 import android.os.StrictMode.ThreadPolicy; 43 import android.os.SystemClock; 44 import android.platform.test.annotations.AppModeFull; 45 import android.platform.test.annotations.Presubmit; 46 import android.print.PageRange; 47 import android.print.PrintAttributes; 48 import android.print.PrintDocumentAdapter; 49 import android.print.PrintDocumentAdapter.LayoutResultCallback; 50 import android.print.PrintDocumentAdapter.WriteResultCallback; 51 import android.print.PrintDocumentInfo; 52 import android.test.ActivityInstrumentationTestCase2; 53 import android.test.UiThreadTest; 54 import android.util.AttributeSet; 55 import android.util.DisplayMetrics; 56 import android.view.KeyEvent; 57 import android.view.MotionEvent; 58 import android.view.View; 59 import android.view.ViewGroup; 60 import android.view.textclassifier.TextClassification; 61 import android.view.textclassifier.TextClassifier; 62 import android.view.textclassifier.TextSelection; 63 import android.webkit.ConsoleMessage; 64 import android.webkit.CookieSyncManager; 65 import android.webkit.DownloadListener; 66 import android.webkit.JavascriptInterface; 67 import android.webkit.SafeBrowsingResponse; 68 import android.webkit.ValueCallback; 69 import android.webkit.WebBackForwardList; 70 import android.webkit.WebChromeClient; 71 import android.webkit.WebIconDatabase; 72 import android.webkit.WebResourceRequest; 73 import android.webkit.WebSettings; 74 import android.webkit.WebView; 75 import android.webkit.WebView.HitTestResult; 76 import android.webkit.WebView.PictureListener; 77 import android.webkit.WebView.VisualStateCallback; 78 import android.webkit.WebViewClient; 79 import android.webkit.WebViewDatabase; 80 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient; 81 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient; 82 import android.widget.LinearLayout; 83 84 import androidx.test.filters.FlakyTest; 85 86 import com.android.compatibility.common.util.NullWebViewUtils; 87 import com.android.compatibility.common.util.PollingCheck; 88 import com.google.common.util.concurrent.SettableFuture; 89 90 import java.io.ByteArrayInputStream; 91 import java.io.File; 92 import java.io.FileInputStream; 93 import java.io.FileNotFoundException; 94 import java.io.IOException; 95 96 import java.net.MalformedURLException; 97 import java.net.URL; 98 99 import java.nio.charset.Charset; 100 import java.nio.charset.StandardCharsets; 101 102 import java.util.Collections; 103 import java.util.Date; 104 import java.util.concurrent.atomic.AtomicBoolean; 105 import java.util.concurrent.atomic.AtomicReference; 106 import java.util.concurrent.Callable; 107 import java.util.concurrent.Future; 108 import java.util.concurrent.FutureTask; 109 import java.util.concurrent.Semaphore; 110 import java.util.concurrent.TimeUnit; 111 import java.util.ArrayList; 112 import java.util.HashMap; 113 import java.util.List; 114 import java.util.Map; 115 116 import org.apache.http.Header; 117 import org.apache.http.HttpEntity; 118 import org.apache.http.HttpEntityEnclosingRequest; 119 import org.apache.http.HttpRequest; 120 import org.apache.http.util.EncodingUtils; 121 import org.apache.http.util.EntityUtils; 122 123 @AppModeFull 124 public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> { 125 private static final int INITIAL_PROGRESS = 100; 126 private static final String X_REQUESTED_WITH = "X-Requested-With"; 127 private static final String PRINTER_TEST_FILE = "print.pdf"; 128 private static final String PDF_PREAMBLE = "%PDF-1"; 129 // Snippet of HTML that will prevent favicon requests to the test server. 130 private static final String HTML_HEADER = 131 "<html><head><link rel=\"shortcut icon\" href=\"%23\" /></head>"; 132 private static final String SIMPLE_HTML = "<html><body>simple html</body></html>"; 133 134 /** 135 * This is the minimum number of milliseconds to wait for scrolling to 136 * start. If no scrolling has started before this timeout then it is 137 * assumed that no scrolling will happen. 138 */ 139 private static final long MIN_SCROLL_WAIT_MS = 1000; 140 141 /** 142 * This is the minimum number of milliseconds to wait for findAll to 143 * find all the matches. If matches are not found, the Listener would 144 * call findAll again until it times out. 145 */ 146 private static final long MIN_FIND_WAIT_MS = 3000; 147 148 /** 149 * Once scrolling has started, this is the interval that scrolling 150 * is checked to see if there is a change. If no scrolling change 151 * has happened in the given time then it is assumed that scrolling 152 * has stopped. 153 */ 154 private static final long SCROLL_WAIT_INTERVAL_MS = 200; 155 156 private WebView mWebView; 157 private CtsTestServer mWebServer; 158 private WebViewOnUiThread mOnUiThread; 159 private WebIconDatabase mIconDb; 160 WebViewTest()161 public WebViewTest() { 162 super("com.android.cts.webkit", WebViewCtsActivity.class); 163 } 164 165 @Override setUp()166 protected void setUp() throws Exception { 167 super.setUp(); 168 final WebViewCtsActivity activity = getActivity(); 169 mWebView = activity.getWebView(); 170 if (mWebView != null) { 171 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 172 @Override 173 protected boolean check() { 174 return activity.hasWindowFocus(); 175 } 176 }.run(); 177 File f = activity.getFileStreamPath("snapshot"); 178 if (f.exists()) { 179 f.delete(); 180 } 181 182 mOnUiThread = new WebViewOnUiThread(mWebView); 183 } 184 } 185 186 @Override tearDown()187 protected void tearDown() throws Exception { 188 if (mOnUiThread != null) { 189 mOnUiThread.cleanUp(); 190 } 191 if (mWebServer != null) { 192 stopWebServer(); 193 } 194 if (mIconDb != null) { 195 mIconDb.removeAllIcons(); 196 mIconDb.close(); 197 mIconDb = null; 198 } 199 super.tearDown(); 200 } 201 startWebServer(boolean secure)202 private void startWebServer(boolean secure) throws Exception { 203 assertNull(mWebServer); 204 mWebServer = new CtsTestServer(getActivity(), secure); 205 } 206 stopWebServer()207 private void stopWebServer() throws Exception { 208 assertNotNull(mWebServer); 209 ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 210 ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy) 211 .permitNetwork() 212 .build(); 213 StrictMode.setThreadPolicy(tmpPolicy); 214 mWebServer.shutdown(); 215 mWebServer = null; 216 StrictMode.setThreadPolicy(oldPolicy); 217 } 218 219 @UiThreadTest testConstructor()220 public void testConstructor() { 221 if (!NullWebViewUtils.isWebViewAvailable()) { 222 return; 223 } 224 225 WebView webView = new WebView(getActivity()); 226 webView.destroy(); 227 webView = new WebView(getActivity(), null); 228 webView.destroy(); 229 webView = new WebView(getActivity(), null, 0); 230 webView.destroy(); 231 } 232 233 @UiThreadTest testCreatingWebViewWithDeviceEncrpytionFails()234 public void testCreatingWebViewWithDeviceEncrpytionFails() { 235 if (!NullWebViewUtils.isWebViewAvailable()) { 236 return; 237 } 238 239 Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext(); 240 try { 241 new WebView(deviceEncryptedContext); 242 fail("WebView should have thrown exception when creating with a device " + 243 "protected storage context"); 244 } catch (IllegalArgumentException e) {} 245 } 246 247 @UiThreadTest testCreatingWebViewWithMultipleEncryptionContext()248 public void testCreatingWebViewWithMultipleEncryptionContext() { 249 if (!NullWebViewUtils.isWebViewAvailable()) { 250 return; 251 } 252 253 // Credential encrpytion is the default. Create one here for the sake of clarity. 254 Context credentialEncryptedContext = getActivity().createCredentialProtectedStorageContext(); 255 Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext(); 256 257 // No exception should be thrown with credential encryption context. 258 WebView webView = new WebView(credentialEncryptedContext); 259 webView.destroy(); 260 261 try { 262 new WebView(deviceEncryptedContext); 263 fail("WebView should have thrown exception when creating with a device " + 264 "protected storage context"); 265 } catch (IllegalArgumentException e) {} 266 } 267 268 @UiThreadTest testCreatingWebViewCreatesCookieSyncManager()269 public void testCreatingWebViewCreatesCookieSyncManager() throws Exception { 270 if (!NullWebViewUtils.isWebViewAvailable()) { 271 return; 272 } 273 WebView webView = new WebView(getActivity()); 274 assertNotNull(CookieSyncManager.getInstance()); 275 webView.destroy(); 276 } 277 278 // Static methods should be safe to call on non-UI threads testFindAddress()279 public void testFindAddress() { 280 if (!NullWebViewUtils.isWebViewAvailable()) { 281 return; 282 } 283 284 /* 285 * Info about USPS 286 * http://en.wikipedia.org/wiki/Postal_address#United_States 287 * http://www.usps.com/ 288 */ 289 // full address 290 assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826", 291 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826")); 292 // Zipcode is optional. 293 assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA", 294 WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA")); 295 // not an address 296 assertNull(WebView.findAddress("This is not an address: no town, no state, no zip.")); 297 298 // would be an address, except for numbers that are not ASCII 299 assertNull(WebView.findAddress( 300 "80\uD835\uDFEF \uD835\uDFEF\uD835\uDFEFth Avenue Sunnyvale, CA 94089")); 301 } 302 303 @UiThreadTest testScrollBarOverlay()304 public void testScrollBarOverlay() throws Throwable { 305 if (!NullWebViewUtils.isWebViewAvailable()) { 306 return; 307 } 308 309 // These functions have no effect; just verify they don't crash 310 mWebView.setHorizontalScrollbarOverlay(true); 311 mWebView.setVerticalScrollbarOverlay(false); 312 313 assertTrue(mWebView.overlayHorizontalScrollbar()); 314 assertFalse(mWebView.overlayVerticalScrollbar()); 315 } 316 317 @Presubmit 318 @UiThreadTest testLoadUrl()319 public void testLoadUrl() throws Exception { 320 if (!NullWebViewUtils.isWebViewAvailable()) { 321 return; 322 } 323 324 assertNull(mWebView.getUrl()); 325 assertNull(mWebView.getOriginalUrl()); 326 assertEquals(INITIAL_PROGRESS, mWebView.getProgress()); 327 328 startWebServer(false); 329 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 330 mOnUiThread.loadUrlAndWaitForCompletion(url); 331 assertEquals(100, mWebView.getProgress()); 332 assertEquals(url, mWebView.getUrl()); 333 assertEquals(url, mWebView.getOriginalUrl()); 334 assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle()); 335 336 // verify that the request also includes X-Requested-With header 337 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 338 Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); 339 assertEquals(1, matchingHeaders.length); 340 341 Header header = matchingHeaders[0]; 342 assertEquals(mWebView.getContext().getApplicationInfo().packageName, header.getValue()); 343 } 344 345 @UiThreadTest testPostUrlWithNonNetworkUrl()346 public void testPostUrlWithNonNetworkUrl() throws Exception { 347 if (!NullWebViewUtils.isWebViewAvailable()) { 348 return; 349 } 350 final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL; 351 352 mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]); 353 354 assertEquals("Non-network URL should have loaded", TestHtmlConstants.HELLO_WORLD_TITLE, 355 mWebView.getTitle()); 356 } 357 358 @UiThreadTest testPostUrlWithNetworkUrl()359 public void testPostUrlWithNetworkUrl() throws Exception { 360 if (!NullWebViewUtils.isWebViewAvailable()) { 361 return; 362 } 363 startWebServer(false); 364 final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 365 final String postDataString = "username=my_username&password=my_password"; 366 final byte[] postData = EncodingUtils.getBytes(postDataString, "BASE64"); 367 368 mOnUiThread.postUrlAndWaitForCompletion(networkUrl, postData); 369 370 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 371 assertEquals("The last request should be POST", request.getRequestLine().getMethod(), 372 "POST"); 373 374 assertTrue("The last request should have a request body", 375 request instanceof HttpEntityEnclosingRequest); 376 HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); 377 String entityString = EntityUtils.toString(entity); 378 assertEquals(entityString, postDataString); 379 } 380 381 @UiThreadTest testLoadUrlDoesNotStripParamsWhenLoadingContentUrls()382 public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception { 383 if (!NullWebViewUtils.isWebViewAvailable()) { 384 return; 385 } 386 387 Uri.Builder uriBuilder = new Uri.Builder().scheme( 388 ContentResolver.SCHEME_CONTENT).authority(MockContentProvider.AUTHORITY); 389 uriBuilder.appendPath("foo.html").appendQueryParameter("param","bar"); 390 String url = uriBuilder.build().toString(); 391 mOnUiThread.loadUrlAndWaitForCompletion(url); 392 // verify the parameter is not stripped. 393 Uri uri = Uri.parse(mWebView.getTitle()); 394 assertEquals("bar", uri.getQueryParameter("param")); 395 } 396 397 @UiThreadTest testAppInjectedXRequestedWithHeaderIsNotOverwritten()398 public void testAppInjectedXRequestedWithHeaderIsNotOverwritten() throws Exception { 399 if (!NullWebViewUtils.isWebViewAvailable()) { 400 return; 401 } 402 403 startWebServer(false); 404 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 405 HashMap<String, String> map = new HashMap<String, String>(); 406 final String requester = "foo"; 407 map.put(X_REQUESTED_WITH, requester); 408 mOnUiThread.loadUrlAndWaitForCompletion(url, map); 409 410 // verify that the request also includes X-Requested-With header 411 // but is not overwritten by the webview 412 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 413 Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); 414 assertEquals(1, matchingHeaders.length); 415 416 Header header = matchingHeaders[0]; 417 assertEquals(requester, header.getValue()); 418 } 419 420 @UiThreadTest testAppCanInjectHeadersViaImmutableMap()421 public void testAppCanInjectHeadersViaImmutableMap() throws Exception { 422 if (!NullWebViewUtils.isWebViewAvailable()) { 423 return; 424 } 425 426 startWebServer(false); 427 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 428 HashMap<String, String> map = new HashMap<String, String>(); 429 final String requester = "foo"; 430 map.put(X_REQUESTED_WITH, requester); 431 mOnUiThread.loadUrlAndWaitForCompletion(url, Collections.unmodifiableMap(map)); 432 433 // verify that the request also includes X-Requested-With header 434 // but is not overwritten by the webview 435 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 436 Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH); 437 assertEquals(1, matchingHeaders.length); 438 439 Header header = matchingHeaders[0]; 440 assertEquals(requester, header.getValue()); 441 } 442 testCanInjectHeaders()443 public void testCanInjectHeaders() throws Exception { 444 if (!NullWebViewUtils.isWebViewAvailable()) { 445 return; 446 } 447 448 final String X_FOO = "X-foo"; 449 final String X_FOO_VALUE = "test"; 450 451 final String X_REFERER = "Referer"; 452 final String X_REFERER_VALUE = "http://www.example.com/"; 453 startWebServer(false); 454 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 455 HashMap<String, String> map = new HashMap<String, String>(); 456 map.put(X_FOO, X_FOO_VALUE); 457 map.put(X_REFERER, X_REFERER_VALUE); 458 mOnUiThread.loadUrlAndWaitForCompletion(url, map); 459 460 HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL); 461 for (Map.Entry<String,String> value : map.entrySet()) { 462 String header = value.getKey(); 463 Header[] matchingHeaders = request.getHeaders(header); 464 assertEquals("header " + header + " not found", 1, matchingHeaders.length); 465 assertEquals(value.getValue(), matchingHeaders[0].getValue()); 466 } 467 } 468 469 @SuppressWarnings("deprecation") 470 @UiThreadTest testGetVisibleTitleHeight()471 public void testGetVisibleTitleHeight() throws Exception { 472 if (!NullWebViewUtils.isWebViewAvailable()) { 473 return; 474 } 475 476 startWebServer(false); 477 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 478 mOnUiThread.loadUrlAndWaitForCompletion(url); 479 assertEquals(0, mWebView.getVisibleTitleHeight()); 480 } 481 482 @UiThreadTest testGetOriginalUrl()483 public void testGetOriginalUrl() throws Throwable { 484 if (!NullWebViewUtils.isWebViewAvailable()) { 485 return; 486 } 487 488 startWebServer(false); 489 final String finalUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 490 final String redirectUrl = 491 mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 492 493 assertNull(mWebView.getUrl()); 494 assertNull(mWebView.getOriginalUrl()); 495 496 // By default, WebView sends an intent to ask the system to 497 // handle loading a new URL. We set a WebViewClient as 498 // WebViewClient.shouldOverrideUrlLoading() returns false, so 499 // the WebView will load the new URL. 500 mWebView.setWebViewClient(new WaitForLoadedClient(mOnUiThread)); 501 mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl); 502 503 assertEquals(finalUrl, mWebView.getUrl()); 504 assertEquals(redirectUrl, mWebView.getOriginalUrl()); 505 } 506 testStopLoading()507 public void testStopLoading() throws Exception { 508 if (!NullWebViewUtils.isWebViewAvailable()) { 509 return; 510 } 511 512 assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress()); 513 514 startWebServer(false); 515 String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL); 516 517 class JsInterface { 518 private boolean mPageLoaded; 519 520 @JavascriptInterface 521 public synchronized void pageLoaded() { 522 mPageLoaded = true; 523 notify(); 524 } 525 public synchronized boolean getPageLoaded() { 526 return mPageLoaded; 527 } 528 } 529 530 JsInterface jsInterface = new JsInterface(); 531 532 mOnUiThread.getSettings().setJavaScriptEnabled(true); 533 mOnUiThread.addJavascriptInterface(jsInterface, "javabridge"); 534 mOnUiThread.loadUrl(url); 535 mOnUiThread.stopLoading(); 536 537 // We wait to see that the onload callback in the HTML is not fired. 538 synchronized (jsInterface) { 539 jsInterface.wait(3000); 540 } 541 542 assertFalse(jsInterface.getPageLoaded()); 543 } 544 545 @UiThreadTest testGoBackAndForward()546 public void testGoBackAndForward() throws Exception { 547 if (!NullWebViewUtils.isWebViewAvailable()) { 548 return; 549 } 550 551 assertGoBackOrForwardBySteps(false, -1); 552 assertGoBackOrForwardBySteps(false, 1); 553 554 startWebServer(false); 555 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 556 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 557 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 558 559 mOnUiThread.loadUrlAndWaitForCompletion(url1); 560 pollingCheckWebBackForwardList(url1, 0, 1); 561 assertGoBackOrForwardBySteps(false, -1); 562 assertGoBackOrForwardBySteps(false, 1); 563 564 mOnUiThread.loadUrlAndWaitForCompletion(url2); 565 pollingCheckWebBackForwardList(url2, 1, 2); 566 assertGoBackOrForwardBySteps(true, -1); 567 assertGoBackOrForwardBySteps(false, 1); 568 569 mOnUiThread.loadUrlAndWaitForCompletion(url3); 570 pollingCheckWebBackForwardList(url3, 2, 3); 571 assertGoBackOrForwardBySteps(true, -2); 572 assertGoBackOrForwardBySteps(false, 1); 573 574 mWebView.goBack(); 575 pollingCheckWebBackForwardList(url2, 1, 3); 576 assertGoBackOrForwardBySteps(true, -1); 577 assertGoBackOrForwardBySteps(true, 1); 578 579 mWebView.goForward(); 580 pollingCheckWebBackForwardList(url3, 2, 3); 581 assertGoBackOrForwardBySteps(true, -2); 582 assertGoBackOrForwardBySteps(false, 1); 583 584 mWebView.goBackOrForward(-2); 585 pollingCheckWebBackForwardList(url1, 0, 3); 586 assertGoBackOrForwardBySteps(false, -1); 587 assertGoBackOrForwardBySteps(true, 2); 588 589 mWebView.goBackOrForward(2); 590 pollingCheckWebBackForwardList(url3, 2, 3); 591 assertGoBackOrForwardBySteps(true, -2); 592 assertGoBackOrForwardBySteps(false, 1); 593 } 594 testAddJavascriptInterface()595 public void testAddJavascriptInterface() throws Exception { 596 if (!NullWebViewUtils.isWebViewAvailable()) { 597 return; 598 } 599 600 mOnUiThread.getSettings().setJavaScriptEnabled(true); 601 mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); 602 603 final class TestJavaScriptInterface { 604 private boolean mWasProvideResultCalled; 605 private String mResult; 606 607 private synchronized String waitForResult() { 608 while (!mWasProvideResultCalled) { 609 try { 610 wait(WebkitUtils.TEST_TIMEOUT_MS); 611 } catch (InterruptedException e) { 612 continue; 613 } 614 if (!mWasProvideResultCalled) { 615 fail("Unexpected timeout"); 616 } 617 } 618 return mResult; 619 } 620 621 public synchronized boolean wasProvideResultCalled() { 622 return mWasProvideResultCalled; 623 } 624 625 @JavascriptInterface 626 public synchronized void provideResult(String result) { 627 mWasProvideResultCalled = true; 628 mResult = result; 629 notify(); 630 } 631 } 632 633 final TestJavaScriptInterface obj = new TestJavaScriptInterface(); 634 mOnUiThread.addJavascriptInterface(obj, "interface"); 635 assertFalse(obj.wasProvideResultCalled()); 636 637 startWebServer(false); 638 String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL); 639 mOnUiThread.loadUrlAndWaitForCompletion(url); 640 assertEquals("Original title", obj.waitForResult()); 641 642 // Verify that only methods annotated with @JavascriptInterface are exposed 643 // on the JavaScript interface object. 644 assertEquals("\"function\"", 645 mOnUiThread.evaluateJavascriptSync("typeof interface.provideResult")); 646 647 assertEquals("\"undefined\"", 648 mOnUiThread.evaluateJavascriptSync("typeof interface.wasProvideResultCalled")); 649 650 assertEquals("\"undefined\"", 651 mOnUiThread.evaluateJavascriptSync("typeof interface.getClass")); 652 } 653 testAddJavascriptInterfaceNullObject()654 public void testAddJavascriptInterfaceNullObject() throws Exception { 655 if (!NullWebViewUtils.isWebViewAvailable()) { 656 return; 657 } 658 659 mOnUiThread.getSettings().setJavaScriptEnabled(true); 660 String setTitleToPropertyTypeHtml = "<html><head></head>" + 661 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>"; 662 663 // Test that the property is initially undefined. 664 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 665 "text/html", null); 666 assertEquals("undefined", mOnUiThread.getTitle()); 667 668 // Test that adding a null object has no effect. 669 mOnUiThread.addJavascriptInterface(null, "injectedObject"); 670 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 671 "text/html", null); 672 assertEquals("undefined", mOnUiThread.getTitle()); 673 674 // Test that adding an object gives an object type. 675 final Object obj = new Object(); 676 mOnUiThread.addJavascriptInterface(obj, "injectedObject"); 677 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 678 "text/html", null); 679 assertEquals("object", mOnUiThread.getTitle()); 680 681 // Test that trying to replace with a null object has no effect. 682 mOnUiThread.addJavascriptInterface(null, "injectedObject"); 683 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 684 "text/html", null); 685 assertEquals("object", mOnUiThread.getTitle()); 686 } 687 testRemoveJavascriptInterface()688 public void testRemoveJavascriptInterface() throws Exception { 689 if (!NullWebViewUtils.isWebViewAvailable()) { 690 return; 691 } 692 693 mOnUiThread.getSettings().setJavaScriptEnabled(true); 694 String setTitleToPropertyTypeHtml = "<html><head></head>" + 695 "<body onload=\"document.title = typeof window.injectedObject;\"></body></html>"; 696 697 // Test that adding an object gives an object type. 698 mOnUiThread.addJavascriptInterface(new Object(), "injectedObject"); 699 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 700 "text/html", null); 701 assertEquals("object", mOnUiThread.getTitle()); 702 703 // Test that reloading the page after removing the object leaves the property undefined. 704 mOnUiThread.removeJavascriptInterface("injectedObject"); 705 mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml, 706 "text/html", null); 707 assertEquals("undefined", mOnUiThread.getTitle()); 708 } 709 testUseRemovedJavascriptInterface()710 public void testUseRemovedJavascriptInterface() throws Throwable { 711 if (!NullWebViewUtils.isWebViewAvailable()) { 712 return; 713 } 714 715 class RemovedObject { 716 @Override 717 @JavascriptInterface 718 public String toString() { 719 return "removedObject"; 720 } 721 722 @JavascriptInterface 723 public void remove() throws Throwable { 724 mOnUiThread.removeJavascriptInterface("removedObject"); 725 System.gc(); 726 } 727 } 728 class ResultObject { 729 private String mResult; 730 private boolean mIsResultAvailable; 731 732 @JavascriptInterface 733 public synchronized void setResult(String result) { 734 mResult = result; 735 mIsResultAvailable = true; 736 notify(); 737 } 738 public synchronized String getResult() { 739 while (!mIsResultAvailable) { 740 try { 741 wait(); 742 } catch (InterruptedException e) { 743 } 744 } 745 return mResult; 746 } 747 } 748 final ResultObject resultObject = new ResultObject(); 749 750 // Test that an object is still usable if removed while the page is in use, even if we have 751 // no external references to it. 752 mOnUiThread.getSettings().setJavaScriptEnabled(true); 753 mOnUiThread.addJavascriptInterface(new RemovedObject(), "removedObject"); 754 mOnUiThread.addJavascriptInterface(resultObject, "resultObject"); 755 mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" + 756 "<body onload=\"window.removedObject.remove();" + 757 "resultObject.setResult(removedObject.toString());\"></body></html>", 758 "text/html", null); 759 assertEquals("removedObject", resultObject.getResult()); 760 } 761 testAddJavascriptInterfaceExceptions()762 public void testAddJavascriptInterfaceExceptions() throws Exception { 763 if (!NullWebViewUtils.isWebViewAvailable()) { 764 return; 765 } 766 WebSettings settings = mOnUiThread.getSettings(); 767 settings.setJavaScriptEnabled(true); 768 settings.setJavaScriptCanOpenWindowsAutomatically(true); 769 770 final AtomicBoolean mJsInterfaceWasCalled = new AtomicBoolean(false) { 771 @JavascriptInterface 772 public synchronized void call() { 773 set(true); 774 // The main purpose of this test is to ensure an exception here does not 775 // crash the implementation. 776 throw new RuntimeException("Javascript Interface exception"); 777 } 778 }; 779 780 mOnUiThread.addJavascriptInterface(mJsInterfaceWasCalled, "interface"); 781 782 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 783 784 assertFalse(mJsInterfaceWasCalled.get()); 785 786 assertEquals("\"pass\"", mOnUiThread.evaluateJavascriptSync( 787 "try {interface.call(); 'fail'; } catch (exception) { 'pass'; } ")); 788 assertTrue(mJsInterfaceWasCalled.get()); 789 } 790 testJavascriptInterfaceCustomPropertiesClearedOnReload()791 public void testJavascriptInterfaceCustomPropertiesClearedOnReload() throws Exception { 792 if (!NullWebViewUtils.isWebViewAvailable()) { 793 return; 794 } 795 796 mOnUiThread.getSettings().setJavaScriptEnabled(true); 797 798 mOnUiThread.addJavascriptInterface(new Object(), "interface"); 799 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 800 801 assertEquals("42", mOnUiThread.evaluateJavascriptSync("interface.custom_property = 42")); 802 803 assertEquals("true", mOnUiThread.evaluateJavascriptSync("'custom_property' in interface")); 804 805 mOnUiThread.reloadAndWaitForCompletion(); 806 807 assertEquals("false", mOnUiThread.evaluateJavascriptSync("'custom_property' in interface")); 808 } 809 810 @FlakyTest(bugId = 171702662) testJavascriptInterfaceForClientPopup()811 public void testJavascriptInterfaceForClientPopup() throws Exception { 812 if (!NullWebViewUtils.isWebViewAvailable()) { 813 return; 814 } 815 816 mOnUiThread.getSettings().setJavaScriptEnabled(true); 817 mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); 818 mOnUiThread.getSettings().setSupportMultipleWindows(true); 819 820 class TestJavaScriptInterface { 821 @JavascriptInterface 822 public int test() { 823 return 42; 824 } 825 } 826 final TestJavaScriptInterface obj = new TestJavaScriptInterface(); 827 828 final WebView childWebView = mOnUiThread.createWebView(); 829 WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(childWebView); 830 childOnUiThread.getSettings().setJavaScriptEnabled(true); 831 childOnUiThread.addJavascriptInterface(obj, "interface"); 832 833 final SettableFuture<Void> onCreateWindowFuture = SettableFuture.create(); 834 mOnUiThread.setWebChromeClient(new WebViewSyncLoader.WaitForProgressClient(mOnUiThread) { 835 @Override 836 public boolean onCreateWindow( 837 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 838 getActivity().addContentView(childWebView, new ViewGroup.LayoutParams( 839 ViewGroup.LayoutParams.FILL_PARENT, 840 ViewGroup.LayoutParams.WRAP_CONTENT)); 841 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; 842 transport.setWebView(childWebView); 843 resultMsg.sendToTarget(); 844 onCreateWindowFuture.set(null); 845 return true; 846 } 847 }); 848 849 startWebServer(false); 850 mOnUiThread.loadUrlAndWaitForCompletion(mWebServer. 851 getAssetUrl(TestHtmlConstants.POPUP_URL)); 852 WebkitUtils.waitForFuture(onCreateWindowFuture); 853 854 childOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 855 856 assertEquals("true", childOnUiThread.evaluateJavascriptSync("'interface' in window")); 857 858 assertEquals("The injected object should be functional", "42", 859 childOnUiThread.evaluateJavascriptSync("interface.test()")); 860 } 861 862 private final class TestPictureListener implements PictureListener { 863 public int callCount; 864 865 @Override onNewPicture(WebView view, Picture picture)866 public void onNewPicture(WebView view, Picture picture) { 867 // Need to inform the listener tracking new picture 868 // for the "page loaded" knowledge since it has been replaced. 869 mOnUiThread.onNewPicture(); 870 this.callCount += 1; 871 } 872 } 873 waitForPictureToHaveColor(int color, final TestPictureListener listener)874 private Picture waitForPictureToHaveColor(int color, 875 final TestPictureListener listener) throws Throwable { 876 final int MAX_ON_NEW_PICTURE_ITERATIONS = 5; 877 final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>(); 878 for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) { 879 final int oldCallCount = listener.callCount; 880 WebkitUtils.onMainThreadSync(() -> { 881 pictureRef.set(mWebView.capturePicture()); 882 }); 883 if (isPictureFilledWithColor(pictureRef.get(), color)) 884 break; 885 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 886 @Override 887 protected boolean check() { 888 return listener.callCount > oldCallCount; 889 } 890 }.run(); 891 } 892 return pictureRef.get(); 893 } 894 testCapturePicture()895 public void testCapturePicture() throws Exception, Throwable { 896 if (!NullWebViewUtils.isWebViewAvailable()) { 897 return; 898 } 899 final TestPictureListener listener = new TestPictureListener(); 900 901 startWebServer(false); 902 final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL); 903 mOnUiThread.setPictureListener(listener); 904 // Showing the blank page will fill the picture with the background color. 905 mOnUiThread.loadUrlAndWaitForCompletion(url); 906 // The default background color is white. 907 Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener); 908 909 WebkitUtils.onMainThread(() -> { 910 mWebView.setBackgroundColor(Color.CYAN); 911 }); 912 mOnUiThread.reloadAndWaitForCompletion(); 913 waitForPictureToHaveColor(Color.CYAN, listener); 914 915 assertTrue("The content of the previously captured picture should not update automatically", 916 isPictureFilledWithColor(oldPicture, Color.WHITE)); 917 } 918 testSetPictureListener()919 public void testSetPictureListener() throws Exception, Throwable { 920 if (!NullWebViewUtils.isWebViewAvailable()) { 921 return; 922 } 923 final class MyPictureListener implements PictureListener { 924 public int callCount; 925 public WebView webView; 926 public Picture picture; 927 928 @Override 929 public void onNewPicture(WebView view, Picture picture) { 930 // Need to inform the listener tracking new picture 931 // for the "page loaded" knowledge since it has been replaced. 932 mOnUiThread.onNewPicture(); 933 this.callCount += 1; 934 this.webView = view; 935 this.picture = picture; 936 } 937 } 938 939 final MyPictureListener listener = new MyPictureListener(); 940 startWebServer(false); 941 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 942 mOnUiThread.setPictureListener(listener); 943 mOnUiThread.loadUrlAndWaitForCompletion(url); 944 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 945 @Override 946 protected boolean check() { 947 return listener.callCount > 0; 948 } 949 }.run(); 950 assertEquals(mWebView, listener.webView); 951 assertNull(listener.picture); 952 953 final int oldCallCount = listener.callCount; 954 final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); 955 mOnUiThread.loadUrlAndWaitForCompletion(newUrl); 956 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 957 @Override 958 protected boolean check() { 959 return listener.callCount > oldCallCount; 960 } 961 }.run(); 962 } 963 964 @UiThreadTest testAccessHttpAuthUsernamePassword()965 public void testAccessHttpAuthUsernamePassword() { 966 if (!NullWebViewUtils.isWebViewAvailable()) { 967 return; 968 } 969 try { 970 WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); 971 972 String host = "http://localhost:8080"; 973 String realm = "testrealm"; 974 String userName = "user"; 975 String password = "password"; 976 977 String[] result = mWebView.getHttpAuthUsernamePassword(host, realm); 978 assertNull(result); 979 980 mWebView.setHttpAuthUsernamePassword(host, realm, userName, password); 981 result = mWebView.getHttpAuthUsernamePassword(host, realm); 982 assertNotNull(result); 983 assertEquals(userName, result[0]); 984 assertEquals(password, result[1]); 985 986 String newPassword = "newpassword"; 987 mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword); 988 result = mWebView.getHttpAuthUsernamePassword(host, realm); 989 assertNotNull(result); 990 assertEquals(userName, result[0]); 991 assertEquals(newPassword, result[1]); 992 993 String newUserName = "newuser"; 994 mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 995 result = mWebView.getHttpAuthUsernamePassword(host, realm); 996 assertNotNull(result); 997 assertEquals(newUserName, result[0]); 998 assertEquals(newPassword, result[1]); 999 1000 // the user is set to null, can not change any thing in the future 1001 mWebView.setHttpAuthUsernamePassword(host, realm, null, password); 1002 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1003 assertNotNull(result); 1004 assertNull(result[0]); 1005 assertEquals(password, result[1]); 1006 1007 mWebView.setHttpAuthUsernamePassword(host, realm, userName, null); 1008 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1009 assertNotNull(result); 1010 assertEquals(userName, result[0]); 1011 assertNull(result[1]); 1012 1013 mWebView.setHttpAuthUsernamePassword(host, realm, null, null); 1014 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1015 assertNotNull(result); 1016 assertNull(result[0]); 1017 assertNull(result[1]); 1018 1019 mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 1020 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1021 assertNotNull(result); 1022 assertEquals(newUserName, result[0]); 1023 assertEquals(newPassword, result[1]); 1024 } finally { 1025 WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); 1026 } 1027 } 1028 1029 @UiThreadTest testWebViewDatabaseAccessHttpAuthUsernamePassword()1030 public void testWebViewDatabaseAccessHttpAuthUsernamePassword() { 1031 if (!NullWebViewUtils.isWebViewAvailable()) { 1032 return; 1033 } 1034 WebViewDatabase webViewDb = WebViewDatabase.getInstance(getActivity()); 1035 try { 1036 webViewDb.clearHttpAuthUsernamePassword(); 1037 1038 String host = "http://localhost:8080"; 1039 String realm = "testrealm"; 1040 String userName = "user"; 1041 String password = "password"; 1042 1043 String[] result = 1044 mWebView.getHttpAuthUsernamePassword(host, 1045 realm); 1046 assertNull(result); 1047 1048 webViewDb.setHttpAuthUsernamePassword(host, realm, userName, password); 1049 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1050 assertNotNull(result); 1051 assertEquals(userName, result[0]); 1052 assertEquals(password, result[1]); 1053 1054 String newPassword = "newpassword"; 1055 webViewDb.setHttpAuthUsernamePassword(host, realm, userName, newPassword); 1056 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1057 assertNotNull(result); 1058 assertEquals(userName, result[0]); 1059 assertEquals(newPassword, result[1]); 1060 1061 String newUserName = "newuser"; 1062 webViewDb.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 1063 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1064 assertNotNull(result); 1065 assertEquals(newUserName, result[0]); 1066 assertEquals(newPassword, result[1]); 1067 1068 // the user is set to null, can not change any thing in the future 1069 webViewDb.setHttpAuthUsernamePassword(host, realm, null, password); 1070 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1071 assertNotNull(result); 1072 assertNull(result[0]); 1073 assertEquals(password, result[1]); 1074 1075 webViewDb.setHttpAuthUsernamePassword(host, realm, userName, null); 1076 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1077 assertNotNull(result); 1078 assertEquals(userName, result[0]); 1079 assertNull(result[1]); 1080 1081 webViewDb.setHttpAuthUsernamePassword(host, realm, null, null); 1082 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1083 assertNotNull(result); 1084 assertNull(result[0]); 1085 assertNull(result[1]); 1086 1087 webViewDb.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 1088 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1089 assertNotNull(result); 1090 assertEquals(newUserName, result[0]); 1091 assertEquals(newPassword, result[1]); 1092 } finally { 1093 webViewDb.clearHttpAuthUsernamePassword(); 1094 } 1095 } 1096 testLoadData()1097 public void testLoadData() throws Throwable { 1098 if (!NullWebViewUtils.isWebViewAvailable()) { 1099 return; 1100 } 1101 final String firstTitle = "Hello, World!"; 1102 final String HTML_CONTENT = 1103 "<html><head><title>" + firstTitle + "</title></head><body></body>" + 1104 "</html>"; 1105 mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT, 1106 "text/html", null); 1107 assertEquals(firstTitle, mOnUiThread.getTitle()); 1108 1109 startWebServer(false); 1110 final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1111 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1112 final String secondTitle = "Foo bar"; 1113 mOnUiThread.loadDataAndWaitForCompletion( 1114 "<html><head><title>" + secondTitle + "</title></head><body onload=\"" + 1115 "document.title = " + 1116 "document.getElementById('frame').contentWindow.location.href;" + 1117 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", 1118 "text/html", null); 1119 assertEquals("Page title should not change, because it should be an error to access a " 1120 + "cross-site frame's href.", 1121 secondTitle, mOnUiThread.getTitle()); 1122 } 1123 testLoadDataWithBaseUrl_resolvesRelativeToBaseUrl()1124 public void testLoadDataWithBaseUrl_resolvesRelativeToBaseUrl() throws Throwable { 1125 if (!NullWebViewUtils.isWebViewAvailable()) { 1126 return; 1127 } 1128 assertNull(mOnUiThread.getUrl()); 1129 String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative 1130 1131 // Trying to resolve a relative URL against a data URL without a base URL 1132 // will fail and we won't make a request to the test web server. 1133 // By using the test web server as the base URL we expect to see a request 1134 // for the relative URL in the test server. 1135 startWebServer(false); 1136 final String baseUrl = mWebServer.getAssetUrl("foo.html"); 1137 mWebServer.resetRequestState(); 1138 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1139 HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>", 1140 "text/html", "UTF-8", null); 1141 assertTrue("The resource request should make it to the server", 1142 mWebServer.wasResourceRequested(imgUrl)); 1143 } 1144 testLoadDataWithBaseUrl_historyUrl()1145 public void testLoadDataWithBaseUrl_historyUrl() throws Throwable { 1146 if (!NullWebViewUtils.isWebViewAvailable()) { 1147 return; 1148 } 1149 final String baseUrl = "http://www.baseurl.com/"; 1150 final String historyUrl = "http://www.example.com/"; 1151 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1152 SIMPLE_HTML, 1153 "text/html", "UTF-8", historyUrl); 1154 assertEquals(historyUrl, mOnUiThread.getUrl()); 1155 } 1156 testLoadDataWithBaseUrl_nullHistoryUrlShowsAsAboutBlank()1157 public void testLoadDataWithBaseUrl_nullHistoryUrlShowsAsAboutBlank() throws Throwable { 1158 if (!NullWebViewUtils.isWebViewAvailable()) { 1159 return; 1160 } 1161 // Check that reported URL is "about:blank" when supplied history URL 1162 // is null. 1163 final String baseUrl = "http://www.baseurl.com/"; 1164 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1165 SIMPLE_HTML, 1166 "text/html", "UTF-8", null); 1167 assertEquals("about:blank", mOnUiThread.getUrl()); 1168 } 1169 testLoadDataWithBaseUrl_javascriptCanAccessOrigin()1170 public void testLoadDataWithBaseUrl_javascriptCanAccessOrigin() throws Throwable { 1171 if (!NullWebViewUtils.isWebViewAvailable()) { 1172 return; 1173 } 1174 // Test that JavaScript can access content from the same origin as the base URL. 1175 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1176 startWebServer(false); 1177 final String baseUrl = mWebServer.getAssetUrl("foo.html"); 1178 final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1179 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1180 HTML_HEADER + "<body onload=\"" + 1181 "document.title = document.getElementById('frame').contentWindow.location.href;" + 1182 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", 1183 "text/html", "UTF-8", null); 1184 assertEquals(crossOriginUrl, mOnUiThread.getTitle()); 1185 } 1186 testLoadDataWithBaseUrl_dataBaseUrlIgnoresHistoryUrl()1187 public void testLoadDataWithBaseUrl_dataBaseUrlIgnoresHistoryUrl() throws Throwable { 1188 if (!NullWebViewUtils.isWebViewAvailable()) { 1189 return; 1190 } 1191 // Check that when the base URL uses the 'data' scheme, a 'data' scheme URL is used and the 1192 // history URL is ignored. 1193 final String baseUrl = "data:foo"; 1194 final String historyUrl = "http://www.example.com/"; 1195 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1196 SIMPLE_HTML, 1197 "text/html", "UTF-8", historyUrl); 1198 1199 final String currentUrl = mOnUiThread.getUrl(); 1200 assertEquals("Current URL (" + currentUrl + ") should be a data URI", 0, 1201 mOnUiThread.getUrl().indexOf("data:text/html")); 1202 assertThat("Current URL (" + currentUrl + ") should contain the simple HTML we loaded", 1203 mOnUiThread.getUrl().indexOf("simple html"), greaterThan(0)); 1204 } 1205 testLoadDataWithBaseUrl_unencodedContentHttpBaseUrl()1206 public void testLoadDataWithBaseUrl_unencodedContentHttpBaseUrl() throws Throwable { 1207 if (!NullWebViewUtils.isWebViewAvailable()) { 1208 return; 1209 } 1210 // Check that when a non-data: base URL is used, we treat the String to load as 1211 // a raw string and just dump it into the WebView, i.e. not decoding any URL entities. 1212 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com", 1213 HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>", 1214 "text/html", "UTF-8", null); 1215 assertEquals("Hello World%21", mOnUiThread.getTitle()); 1216 } 1217 testLoadDataWithBaseUrl_urlEncodedContentDataBaseUrl()1218 public void testLoadDataWithBaseUrl_urlEncodedContentDataBaseUrl() throws Throwable { 1219 if (!NullWebViewUtils.isWebViewAvailable()) { 1220 return; 1221 } 1222 // Check that when a data: base URL is used, we treat the String to load as a data: URL 1223 // and run load steps such as decoding URL entities (i.e., contrary to the test case 1224 // above.) 1225 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo", 1226 HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null); 1227 assertEquals("Hello World!", mOnUiThread.getTitle()); 1228 } 1229 testLoadDataWithBaseUrl_nullSafe()1230 public void testLoadDataWithBaseUrl_nullSafe() throws Throwable { 1231 if (!NullWebViewUtils.isWebViewAvailable()) { 1232 return; 1233 } 1234 1235 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null); 1236 assertEquals("about:blank", mOnUiThread.getUrl()); 1237 } 1238 deleteIfExists(File file)1239 private void deleteIfExists(File file) throws IOException { 1240 if (file.exists()) { 1241 file.delete(); 1242 } 1243 } 1244 readTextFile(File file, Charset encoding)1245 private String readTextFile(File file, Charset encoding) 1246 throws FileNotFoundException, IOException { 1247 FileInputStream stream = new FileInputStream(file); 1248 byte[] bytes = new byte[(int)file.length()]; 1249 stream.read(bytes); 1250 stream.close(); 1251 return new String(bytes, encoding); 1252 } 1253 doSaveWebArchive(String baseName, boolean autoName, final String expectName)1254 private void doSaveWebArchive(String baseName, boolean autoName, final String expectName) 1255 throws Throwable { 1256 final Semaphore saving = new Semaphore(0); 1257 ValueCallback<String> callback = new ValueCallback<String>() { 1258 @Override 1259 public void onReceiveValue(String savedName) { 1260 assertEquals(expectName, savedName); 1261 saving.release(); 1262 } 1263 }; 1264 1265 mOnUiThread.saveWebArchive(baseName, autoName, callback); 1266 assertTrue(saving.tryAcquire(WebkitUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)); 1267 } 1268 testSaveWebArchive()1269 public void testSaveWebArchive() throws Throwable { 1270 if (!NullWebViewUtils.isWebViewAvailable()) { 1271 return; 1272 } 1273 1274 final String testPage = "testSaveWebArchive test page"; 1275 1276 File dir = getActivity().getFilesDir(); 1277 String dirStr = dir.toString(); 1278 1279 File test = new File(dir, "test.mht"); 1280 deleteIfExists(test); 1281 String testStr = test.getAbsolutePath(); 1282 1283 File index = new File(dir, "index.mht"); 1284 deleteIfExists(index); 1285 String indexStr = index.getAbsolutePath(); 1286 1287 File index1 = new File(dir, "index-1.mht"); 1288 deleteIfExists(index1); 1289 String index1Str = index1.getAbsolutePath(); 1290 1291 mOnUiThread.loadDataAndWaitForCompletion(testPage, "text/html", "UTF-8"); 1292 1293 try { 1294 // Save test.mht 1295 doSaveWebArchive(testStr, false, testStr); 1296 1297 // Check the contents of test.mht 1298 String testMhtml = readTextFile(test, StandardCharsets.UTF_8); 1299 assertTrue(testMhtml.contains(testPage)); 1300 1301 // Save index.mht 1302 doSaveWebArchive(dirStr + "/", true, indexStr); 1303 1304 // Check the contents of index.mht 1305 String indexMhtml = readTextFile(index, StandardCharsets.UTF_8); 1306 assertTrue(indexMhtml.contains(testPage)); 1307 1308 // Save index-1.mht since index.mht already exists 1309 doSaveWebArchive(dirStr + "/", true, index1Str); 1310 1311 // Check the contents of index-1.mht 1312 String index1Mhtml = readTextFile(index1, StandardCharsets.UTF_8); 1313 assertTrue(index1Mhtml.contains(testPage)); 1314 1315 // Try a file in a bogus directory 1316 doSaveWebArchive("/bogus/path/test.mht", false, null); 1317 1318 // Try a bogus directory 1319 doSaveWebArchive("/bogus/path/", true, null); 1320 } finally { 1321 deleteIfExists(test); 1322 deleteIfExists(index); 1323 deleteIfExists(index1); 1324 } 1325 } 1326 1327 private static class WaitForFindResultsListener 1328 implements WebView.FindListener { 1329 private final SettableFuture<Integer> mFuture; 1330 private final WebView mWebView; 1331 private final int mMatchesWanted; 1332 private final String mStringWanted; 1333 private final boolean mRetry; 1334 WaitForFindResultsListener( WebView wv, String wanted, int matches, boolean retry)1335 public WaitForFindResultsListener( 1336 WebView wv, String wanted, int matches, boolean retry) { 1337 mFuture = SettableFuture.create(); 1338 mWebView = wv; 1339 mMatchesWanted = matches; 1340 mStringWanted = wanted; 1341 mRetry = retry; 1342 } 1343 future()1344 public Future<Integer> future() { 1345 return mFuture; 1346 } 1347 1348 @Override onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting)1349 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 1350 boolean isDoneCounting) { 1351 try { 1352 assertEquals("WebView.FindListener callbacks should occur on the UI thread", 1353 Looper.myLooper(), Looper.getMainLooper()); 1354 } catch (Throwable t) { 1355 mFuture.setException(t); 1356 } 1357 if (isDoneCounting) { 1358 //If mRetry set to true and matches aren't equal, call findAll again 1359 if (mRetry && numberOfMatches != mMatchesWanted) { 1360 mWebView.findAll(mStringWanted); 1361 } 1362 else { 1363 mFuture.set(numberOfMatches); 1364 } 1365 } 1366 } 1367 } 1368 testFindAll()1369 public void testFindAll() throws Throwable { 1370 if (!NullWebViewUtils.isWebViewAvailable()) { 1371 return; 1372 } 1373 // Make the page scrollable, so we can detect the scrolling to make sure the 1374 // content fully loaded. 1375 mOnUiThread.setInitialScale(100); 1376 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1377 int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); 1378 // create a paragraph high enough to take up the entire screen 1379 String p = "<p style=\"height:" + dimension + "px;\">" + 1380 "Find all instances of find on the page and highlight them.</p>"; 1381 1382 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1383 + "</body></html>", "text/html", null); 1384 1385 WaitForFindResultsListener l = new WaitForFindResultsListener(mWebView, "find", 2, true); 1386 mOnUiThread.setFindListener(l); 1387 mOnUiThread.findAll("find"); 1388 assertEquals(2, (int)WebkitUtils.waitForFuture(l.future())); 1389 } 1390 testFindNext()1391 public void testFindNext() throws Throwable { 1392 if (!NullWebViewUtils.isWebViewAvailable()) { 1393 return; 1394 } 1395 // Reset the scaling so that finding the next "all" text will require scrolling. 1396 mOnUiThread.setInitialScale(100); 1397 1398 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1399 int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); 1400 // create a paragraph high enough to take up the entire screen 1401 String p = "<p style=\"height:" + dimension + "px;\">" + 1402 "Find all instances of a word on the page and highlight them.</p>"; 1403 1404 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null); 1405 WaitForFindResultsListener l = new WaitForFindResultsListener(mWebView, "all", 2, true); 1406 mOnUiThread.setFindListener(l); 1407 1408 // highlight all the strings found and wait for all the matches to be found 1409 mOnUiThread.findAll("all"); 1410 WebkitUtils.waitForFuture(l.future()); 1411 mOnUiThread.setFindListener(null); 1412 1413 int previousScrollY = mOnUiThread.getScrollY(); 1414 1415 // Focus "all" in the second page and assert that the view scrolls. 1416 mOnUiThread.findNext(true); 1417 waitForScrollingComplete(previousScrollY); 1418 assertThat(mOnUiThread.getScrollY(), greaterThan(previousScrollY)); 1419 previousScrollY = mOnUiThread.getScrollY(); 1420 1421 // Focus "all" in the first page and assert that the view scrolls. 1422 mOnUiThread.findNext(true); 1423 waitForScrollingComplete(previousScrollY); 1424 assertThat(mOnUiThread.getScrollY(), lessThan(previousScrollY)); 1425 previousScrollY = mOnUiThread.getScrollY(); 1426 1427 // Focus "all" in the second page and assert that the view scrolls. 1428 mOnUiThread.findNext(false); 1429 waitForScrollingComplete(previousScrollY); 1430 assertThat(mOnUiThread.getScrollY(), greaterThan(previousScrollY)); 1431 previousScrollY = mOnUiThread.getScrollY(); 1432 1433 // Focus "all" in the first page and assert that the view scrolls. 1434 mOnUiThread.findNext(false); 1435 waitForScrollingComplete(previousScrollY); 1436 assertThat(mOnUiThread.getScrollY(), lessThan(previousScrollY)); 1437 previousScrollY = mOnUiThread.getScrollY(); 1438 1439 // clear the result 1440 mOnUiThread.clearMatches(); 1441 getInstrumentation().waitForIdleSync(); 1442 1443 // can not scroll any more 1444 mOnUiThread.findNext(false); 1445 waitForScrollingComplete(previousScrollY); 1446 assertEquals(mOnUiThread.getScrollY(), previousScrollY); 1447 1448 mOnUiThread.findNext(true); 1449 waitForScrollingComplete(previousScrollY); 1450 assertEquals(mOnUiThread.getScrollY(), previousScrollY); 1451 } 1452 testDocumentHasImages()1453 public void testDocumentHasImages() throws Exception, Throwable { 1454 if (!NullWebViewUtils.isWebViewAvailable()) { 1455 return; 1456 } 1457 final class DocumentHasImageCheckHandler extends Handler { 1458 private SettableFuture<Integer> mFuture; 1459 public DocumentHasImageCheckHandler(Looper looper) { 1460 super(looper); 1461 mFuture = SettableFuture.create(); 1462 } 1463 @Override 1464 public void handleMessage(Message msg) { 1465 mFuture.set(msg.arg1); 1466 } 1467 public Future<Integer> future() { 1468 return mFuture; 1469 } 1470 } 1471 1472 startWebServer(false); 1473 final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); 1474 1475 // Create a handler on the UI thread. 1476 final DocumentHasImageCheckHandler handler = 1477 new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper()); 1478 1479 WebkitUtils.onMainThreadSync(() -> { 1480 mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\"" 1481 + imgUrl + "\"/></body></html>", "text/html", null); 1482 Message response = new Message(); 1483 response.setTarget(handler); 1484 assertFalse(handler.future().isDone()); 1485 mWebView.documentHasImages(response); 1486 }); 1487 assertEquals(1, (int)WebkitUtils.waitForFuture(handler.future())); 1488 } 1489 waitForFlingDone(WebViewOnUiThread webview)1490 private static void waitForFlingDone(WebViewOnUiThread webview) { 1491 class ScrollDiffPollingCheck extends PollingCheck { 1492 private static final long TIME_SLICE = 50; 1493 WebViewOnUiThread mWebView; 1494 private int mScrollX; 1495 private int mScrollY; 1496 1497 ScrollDiffPollingCheck(WebViewOnUiThread webview) { 1498 mWebView = webview; 1499 mScrollX = mWebView.getScrollX(); 1500 mScrollY = mWebView.getScrollY(); 1501 } 1502 1503 @Override 1504 protected boolean check() { 1505 try { 1506 Thread.sleep(TIME_SLICE); 1507 } catch (InterruptedException e) { 1508 // Intentionally ignored. 1509 } 1510 int newScrollX = mWebView.getScrollX(); 1511 int newScrollY = mWebView.getScrollY(); 1512 boolean flingDone = newScrollX == mScrollX && newScrollY == mScrollY; 1513 mScrollX = newScrollX; 1514 mScrollY = newScrollY; 1515 return flingDone; 1516 } 1517 } 1518 new ScrollDiffPollingCheck(webview).run(); 1519 } 1520 testPageScroll()1521 public void testPageScroll() throws Throwable { 1522 if (!NullWebViewUtils.isWebViewAvailable()) { 1523 return; 1524 } 1525 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1526 int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); 1527 String p = "<p style=\"height:" + dimension + "px;\">" + 1528 "Scroll by half the size of the page.</p>"; 1529 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1530 + p + "</body></html>", "text/html", null); 1531 1532 // Wait for UI thread to settle and receive page dimentions from renderer 1533 // such that we can invoke page down. 1534 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1535 @Override 1536 protected boolean check() { 1537 return mOnUiThread.pageDown(false); 1538 } 1539 }.run(); 1540 1541 do { 1542 waitForFlingDone(mOnUiThread); 1543 } while (mOnUiThread.pageDown(false)); 1544 1545 waitForFlingDone(mOnUiThread); 1546 final int bottomScrollY = mOnUiThread.getScrollY(); 1547 1548 assertTrue(mOnUiThread.pageUp(false)); 1549 1550 do { 1551 waitForFlingDone(mOnUiThread); 1552 } while (mOnUiThread.pageUp(false)); 1553 1554 waitForFlingDone(mOnUiThread); 1555 final int topScrollY = mOnUiThread.getScrollY(); 1556 1557 // jump to the bottom 1558 assertTrue(mOnUiThread.pageDown(true)); 1559 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1560 @Override 1561 protected boolean check() { 1562 return bottomScrollY == mOnUiThread.getScrollY(); 1563 } 1564 }.run(); 1565 1566 // jump to the top 1567 assertTrue(mOnUiThread.pageUp(true)); 1568 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1569 @Override 1570 protected boolean check() { 1571 return topScrollY == mOnUiThread.getScrollY(); 1572 } 1573 }.run(); 1574 } 1575 testGetContentHeight()1576 public void testGetContentHeight() throws Throwable { 1577 if (!NullWebViewUtils.isWebViewAvailable()) { 1578 return; 1579 } 1580 mOnUiThread.loadDataAndWaitForCompletion( 1581 "<html><body></body></html>", "text/html", null); 1582 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1583 @Override 1584 protected boolean check() { 1585 return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0 1586 && mOnUiThread.getHeight() != 0; 1587 } 1588 }.run(); 1589 1590 final int tolerance = 2; 1591 // getHeight() returns physical pixels and it is from web contents' size, getContentHeight() 1592 // returns CSS pixels and it is from compositor. In order to compare these two values, we 1593 // need to scale getContentHeight() by the device scale factor. This also amplifies any 1594 // rounding errors. Internally, getHeight() could also have rounding error first and then 1595 // times device scale factor, so we are comparing two rounded numbers below. 1596 // We allow 2 * getScale() as the delta, because getHeight() and getContentHeight() may 1597 // use different rounding algorithms and the results are from different computation 1598 // sequences. The extreme case is that in CSS pixel we have 2 as differences (0.9999 rounded 1599 // down and 1.0001 rounded up), therefore we ended with 2 * getScale(). 1600 assertEquals( 1601 mOnUiThread.getHeight(), 1602 mOnUiThread.getContentHeight() * mOnUiThread.getScale(), 1603 tolerance * Math.max(mOnUiThread.getScale(), 1.0f)); 1604 1605 // Make pageHeight bigger than the larger dimension of the device, so the page is taller 1606 // than viewport. Because when layout_height set to match_parent, getContentHeight() will 1607 // give maximum value between the actual web content height and the viewport height. When 1608 // viewport height is bigger, |extraSpace| below is not the extra space on the web page. 1609 // Note that we are passing physical pixels rather than CSS pixels here, when screen density 1610 // scale is lower than 1.0f, we need to scale it up. 1611 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1612 final float scaleFactor = Math.max(1.0f, 1.0f / mOnUiThread.getScale()); 1613 final int pageHeight = 1614 (int)(Math.ceil(Math.max(metrics.widthPixels, metrics.heightPixels) 1615 * scaleFactor)); 1616 1617 // set the margin to 0 1618 final String p = "<p style=\"height:" + pageHeight 1619 + "px;margin:0px auto;\">Get the height of HTML content.</p>"; 1620 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1621 + "</body></html>", "text/html", null); 1622 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1623 @Override 1624 protected boolean check() { 1625 return mOnUiThread.getContentHeight() > pageHeight; 1626 } 1627 }.run(); 1628 1629 final int extraSpace = mOnUiThread.getContentHeight() - pageHeight; 1630 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1631 + p + "</body></html>", "text/html", null); 1632 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1633 @Override 1634 protected boolean check() { 1635 // |pageHeight| is accurate, |extraSpace| = getContentheight() - |pageHeight|, so it 1636 // might have rounding error +-1, also getContentHeight() might have rounding error 1637 // +-1, so we allow error 2. Note that |pageHeight|, |extraSpace| and 1638 // getContentHeight() are all CSS pixels. 1639 final int expectedContentHeight = pageHeight + pageHeight + extraSpace; 1640 return Math.abs(expectedContentHeight - mOnUiThread.getContentHeight()) 1641 <= tolerance; 1642 } 1643 }.run(); 1644 } 1645 1646 @UiThreadTest testPlatformNotifications()1647 public void testPlatformNotifications() { 1648 if (!NullWebViewUtils.isWebViewAvailable()) { 1649 return; 1650 } 1651 WebView.enablePlatformNotifications(); 1652 WebView.disablePlatformNotifications(); 1653 } 1654 1655 @UiThreadTest testAccessPluginList()1656 public void testAccessPluginList() { 1657 if (!NullWebViewUtils.isWebViewAvailable()) { 1658 return; 1659 } 1660 assertNotNull(WebView.getPluginList()); 1661 1662 // can not find a way to install plugins 1663 mWebView.refreshPlugins(false); 1664 } 1665 1666 @UiThreadTest testDestroy()1667 public void testDestroy() { 1668 if (!NullWebViewUtils.isWebViewAvailable()) { 1669 return; 1670 } 1671 // Create a new WebView, since we cannot call destroy() on a view in the hierarchy 1672 WebView localWebView = new WebView(getActivity()); 1673 localWebView.destroy(); 1674 } 1675 testFlingScroll()1676 public void testFlingScroll() throws Throwable { 1677 if (!NullWebViewUtils.isWebViewAvailable()) { 1678 return; 1679 } 1680 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1681 final int dimension = 10 * Math.max(metrics.widthPixels, metrics.heightPixels); 1682 String p = "<p style=\"height:" + dimension + "px;" + 1683 "width:" + dimension + "px\">Test fling scroll.</p>"; 1684 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1685 + "</body></html>", "text/html", null); 1686 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1687 @Override 1688 protected boolean check() { 1689 return mOnUiThread.getContentHeight() >= dimension; 1690 } 1691 }.run(); 1692 getInstrumentation().waitForIdleSync(); 1693 1694 final int previousScrollX = mOnUiThread.getScrollX(); 1695 final int previousScrollY = mOnUiThread.getScrollY(); 1696 1697 mOnUiThread.flingScroll(10000, 10000); 1698 1699 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1700 @Override 1701 protected boolean check() { 1702 return mOnUiThread.getScrollX() > previousScrollX && 1703 mOnUiThread.getScrollY() > previousScrollY; 1704 } 1705 }.run(); 1706 } 1707 testRequestFocusNodeHref()1708 public void testRequestFocusNodeHref() throws Throwable { 1709 if (!NullWebViewUtils.isWebViewAvailable()) { 1710 return; 1711 } 1712 startWebServer(false); 1713 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1714 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1715 final String links = "<DL><p><DT><A HREF=\"" + url1 1716 + "\">HTML_URL1</A><DT><A HREF=\"" + url2 1717 + "\">HTML_URL2</A></DL><p>"; 1718 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + links + "</body></html>", "text/html", null); 1719 getInstrumentation().waitForIdleSync(); 1720 1721 final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); 1722 final Message hrefMsg = new Message(); 1723 hrefMsg.setTarget(handler); 1724 1725 // focus on first link 1726 handler.reset(); 1727 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 1728 mOnUiThread.requestFocusNodeHref(hrefMsg); 1729 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1730 @Override 1731 protected boolean check() { 1732 boolean done = false; 1733 if (handler.hasCalledHandleMessage()) { 1734 if (handler.mResultUrl != null) { 1735 done = true; 1736 } else { 1737 handler.reset(); 1738 Message newMsg = new Message(); 1739 newMsg.setTarget(handler); 1740 mOnUiThread.requestFocusNodeHref(newMsg); 1741 } 1742 } 1743 return done; 1744 } 1745 }.run(); 1746 assertEquals(url1, handler.getResultUrl()); 1747 1748 // focus on second link 1749 handler.reset(); 1750 final Message hrefMsg2 = new Message(); 1751 hrefMsg2.setTarget(handler); 1752 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 1753 mOnUiThread.requestFocusNodeHref(hrefMsg2); 1754 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1755 @Override 1756 protected boolean check() { 1757 boolean done = false; 1758 final String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1759 if (handler.hasCalledHandleMessage()) { 1760 if (handler.mResultUrl != null && 1761 handler.mResultUrl.equals(url2)) { 1762 done = true; 1763 } else { 1764 handler.reset(); 1765 Message newMsg = new Message(); 1766 newMsg.setTarget(handler); 1767 mOnUiThread.requestFocusNodeHref(newMsg); 1768 } 1769 } 1770 return done; 1771 } 1772 }.run(); 1773 assertEquals(url2, handler.getResultUrl()); 1774 1775 mOnUiThread.requestFocusNodeHref(null); 1776 } 1777 testRequestImageRef()1778 public void testRequestImageRef() throws Exception, Throwable { 1779 if (!NullWebViewUtils.isWebViewAvailable()) { 1780 return; 1781 } 1782 final class ImageLoaded { 1783 public SettableFuture<Void> mImageLoaded = SettableFuture.create(); 1784 1785 @JavascriptInterface 1786 public void loaded() { 1787 mImageLoaded.set(null); 1788 } 1789 1790 public Future<Void> future() { 1791 return mImageLoaded; 1792 } 1793 } 1794 final ImageLoaded imageLoaded = new ImageLoaded(); 1795 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1796 mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded"); 1797 1798 startWebServer(false); 1799 final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL); 1800 mOnUiThread.loadDataAndWaitForCompletion( 1801 "<html><head><title>Title</title><style type='text/css'>" 1802 + "%23imgElement { -webkit-transform: translate3d(0,0,1); }" 1803 + "%23imgElement.finish { -webkit-transform: translate3d(0,0,0);" 1804 + " -webkit-transition-duration: 1ms; }</style>" 1805 + "<script type='text/javascript'>function imgLoad() {" 1806 + "imgElement = document.getElementById('imgElement');" 1807 + "imgElement.addEventListener('webkitTransitionEnd'," 1808 + "function(e) { imageLoaded.loaded(); });" 1809 + "imgElement.className = 'finish';}</script>" 1810 + "</head><body><img id='imgElement' src='" + imgUrl 1811 + "' width='100%' height='100%' onLoad='imgLoad()'/>" 1812 + "</body></html>", "text/html", null); 1813 WebkitUtils.waitForFuture(imageLoaded.future()); 1814 getInstrumentation().waitForIdleSync(); 1815 1816 final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); 1817 final Message msg = new Message(); 1818 msg.setTarget(handler); 1819 1820 // touch the image 1821 handler.reset(); 1822 int[] location = mOnUiThread.getLocationOnScreen(); 1823 int middleX = location[0] + mOnUiThread.getWebView().getWidth() / 2; 1824 int middleY = location[1] + mOnUiThread.getWebView().getHeight() / 2; 1825 1826 long time = SystemClock.uptimeMillis(); 1827 getInstrumentation().sendPointerSync( 1828 MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 1829 middleX, middleY, 0)); 1830 getInstrumentation().waitForIdleSync(); 1831 mOnUiThread.requestImageRef(msg); 1832 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1833 @Override 1834 protected boolean check() { 1835 boolean done = false; 1836 if (handler.hasCalledHandleMessage()) { 1837 if (handler.mResultUrl != null) { 1838 done = true; 1839 } else { 1840 handler.reset(); 1841 Message newMsg = new Message(); 1842 newMsg.setTarget(handler); 1843 mOnUiThread.requestImageRef(newMsg); 1844 } 1845 } 1846 return done; 1847 } 1848 }.run(); 1849 assertEquals(imgUrl, handler.mResultUrl); 1850 } 1851 1852 @UiThreadTest testDebugDump()1853 public void testDebugDump() { 1854 if (!NullWebViewUtils.isWebViewAvailable()) { 1855 return; 1856 } 1857 mWebView.debugDump(); 1858 } 1859 testGetHitTestResult()1860 public void testGetHitTestResult() throws Throwable { 1861 if (!NullWebViewUtils.isWebViewAvailable()) { 1862 return; 1863 } 1864 final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1 1865 + "\">normal anchor</a></p>"; 1866 final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>"; 1867 final String form = "<p><form><input type=\"text\" name=\"Test\"><br>" 1868 + "<input type=\"submit\" value=\"Submit\"></form></p>"; 1869 String phoneNo = "3106984000"; 1870 final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>"; 1871 String email = "test@gmail.com"; 1872 final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>"; 1873 String location = "shanghai"; 1874 final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>"; 1875 1876 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home", 1877 "<html><body>" + anchor + blankAnchor + form + tel + mailto + 1878 geo + "</body></html>", "text/html", "UTF-8", null); 1879 getInstrumentation().waitForIdleSync(); 1880 1881 // anchor 1882 moveFocusDown(); 1883 HitTestResult hitTestResult = mOnUiThread.getHitTestResult(); 1884 assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); 1885 assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra()); 1886 1887 // blank anchor 1888 moveFocusDown(); 1889 hitTestResult = mOnUiThread.getHitTestResult(); 1890 assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); 1891 assertEquals("fake://home", hitTestResult.getExtra()); 1892 1893 // text field 1894 moveFocusDown(); 1895 hitTestResult = mOnUiThread.getHitTestResult(); 1896 assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType()); 1897 assertNull(hitTestResult.getExtra()); 1898 1899 // submit button 1900 moveFocusDown(); 1901 hitTestResult = mOnUiThread.getHitTestResult(); 1902 assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType()); 1903 assertNull(hitTestResult.getExtra()); 1904 1905 // phone number 1906 moveFocusDown(); 1907 hitTestResult = mOnUiThread.getHitTestResult(); 1908 assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType()); 1909 assertEquals(phoneNo, hitTestResult.getExtra()); 1910 1911 // email 1912 moveFocusDown(); 1913 hitTestResult = mOnUiThread.getHitTestResult(); 1914 assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType()); 1915 assertEquals(email, hitTestResult.getExtra()); 1916 1917 // geo address 1918 moveFocusDown(); 1919 hitTestResult = mOnUiThread.getHitTestResult(); 1920 assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType()); 1921 assertEquals(location, hitTestResult.getExtra()); 1922 } 1923 testSetInitialScale()1924 public void testSetInitialScale() throws Throwable { 1925 if (!NullWebViewUtils.isWebViewAvailable()) { 1926 return; 1927 } 1928 final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>"; 1929 final float defaultScale = 1930 getActivity().getResources().getDisplayMetrics().density; 1931 1932 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1933 + "</body></html>", "text/html", null); 1934 1935 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1936 @Override 1937 protected boolean check() { 1938 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1939 } 1940 }.run(); 1941 1942 mOnUiThread.setInitialScale(0); 1943 // modify content to fool WebKit into re-loading 1944 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1945 + "2" + "</body></html>", "text/html", null); 1946 1947 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1948 @Override 1949 protected boolean check() { 1950 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1951 } 1952 }.run(); 1953 1954 mOnUiThread.setInitialScale(50); 1955 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1956 + "3" + "</body></html>", "text/html", null); 1957 1958 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1959 @Override 1960 protected boolean check() { 1961 return Math.abs(0.5 - mOnUiThread.getScale()) < .01f; 1962 } 1963 }.run(); 1964 1965 mOnUiThread.setInitialScale(0); 1966 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1967 + "4" + "</body></html>", "text/html", null); 1968 1969 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 1970 @Override 1971 protected boolean check() { 1972 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1973 } 1974 }.run(); 1975 } 1976 1977 @UiThreadTest testClearHistory()1978 public void testClearHistory() throws Exception { 1979 if (!NullWebViewUtils.isWebViewAvailable()) { 1980 return; 1981 } 1982 startWebServer(false); 1983 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1984 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1985 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 1986 1987 mOnUiThread.loadUrlAndWaitForCompletion(url1); 1988 pollingCheckWebBackForwardList(url1, 0, 1); 1989 1990 mOnUiThread.loadUrlAndWaitForCompletion(url2); 1991 pollingCheckWebBackForwardList(url2, 1, 2); 1992 1993 mOnUiThread.loadUrlAndWaitForCompletion(url3); 1994 pollingCheckWebBackForwardList(url3, 2, 3); 1995 1996 mWebView.clearHistory(); 1997 1998 // only current URL is left after clearing 1999 pollingCheckWebBackForwardList(url3, 0, 1); 2000 } 2001 2002 @UiThreadTest testSaveAndRestoreState()2003 public void testSaveAndRestoreState() throws Throwable { 2004 if (!NullWebViewUtils.isWebViewAvailable()) { 2005 return; 2006 } 2007 assertNull("Should return null when there's nothing to save", 2008 mWebView.saveState(new Bundle())); 2009 2010 startWebServer(false); 2011 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 2012 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 2013 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 2014 2015 // make a history list 2016 mOnUiThread.loadUrlAndWaitForCompletion(url1); 2017 pollingCheckWebBackForwardList(url1, 0, 1); 2018 mOnUiThread.loadUrlAndWaitForCompletion(url2); 2019 pollingCheckWebBackForwardList(url2, 1, 2); 2020 mOnUiThread.loadUrlAndWaitForCompletion(url3); 2021 pollingCheckWebBackForwardList(url3, 2, 3); 2022 2023 // save the list 2024 Bundle bundle = new Bundle(); 2025 WebBackForwardList saveList = mWebView.saveState(bundle); 2026 assertNotNull(saveList); 2027 assertEquals(3, saveList.getSize()); 2028 assertEquals(2, saveList.getCurrentIndex()); 2029 assertEquals(url1, saveList.getItemAtIndex(0).getUrl()); 2030 assertEquals(url2, saveList.getItemAtIndex(1).getUrl()); 2031 assertEquals(url3, saveList.getItemAtIndex(2).getUrl()); 2032 2033 // change the content to a new "blank" web view without history 2034 final WebView newWebView = new WebView(getActivity()); 2035 2036 WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList(); 2037 assertNotNull(copyListBeforeRestore); 2038 assertEquals(0, copyListBeforeRestore.getSize()); 2039 2040 // restore the list 2041 final WebBackForwardList restoreList = newWebView.restoreState(bundle); 2042 assertNotNull(restoreList); 2043 assertEquals(3, restoreList.getSize()); 2044 assertEquals(2, saveList.getCurrentIndex()); 2045 2046 // wait for the list items to get inflated 2047 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 2048 @Override 2049 protected boolean check() { 2050 return restoreList.getItemAtIndex(0).getUrl() != null && 2051 restoreList.getItemAtIndex(1).getUrl() != null && 2052 restoreList.getItemAtIndex(2).getUrl() != null; 2053 } 2054 }.run(); 2055 assertEquals(url1, restoreList.getItemAtIndex(0).getUrl()); 2056 assertEquals(url2, restoreList.getItemAtIndex(1).getUrl()); 2057 assertEquals(url3, restoreList.getItemAtIndex(2).getUrl()); 2058 2059 WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList(); 2060 assertNotNull(copyListAfterRestore); 2061 assertEquals(3, copyListAfterRestore.getSize()); 2062 assertEquals(2, copyListAfterRestore.getCurrentIndex()); 2063 assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl()); 2064 assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl()); 2065 assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl()); 2066 2067 newWebView.destroy(); 2068 } 2069 testRequestChildRectangleOnScreen()2070 public void testRequestChildRectangleOnScreen() throws Throwable { 2071 if (!NullWebViewUtils.isWebViewAvailable()) { 2072 return; 2073 } 2074 2075 // It is needed to make test pass on some devices. 2076 mOnUiThread.setLayoutToMatchParent(); 2077 2078 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 2079 final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); 2080 String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\"> </p>"; 2081 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 2082 + "</body></html>", "text/html", null); 2083 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 2084 @Override 2085 protected boolean check() { 2086 return mOnUiThread.getContentHeight() >= dimension; 2087 } 2088 }.run(); 2089 2090 int origX = mOnUiThread.getScrollX(); 2091 int origY = mOnUiThread.getScrollY(); 2092 2093 int half = dimension / 2; 2094 Rect rect = new Rect(half, half, half + 1, half + 1); 2095 assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true)); 2096 assertThat(mOnUiThread.getScrollX(), greaterThan(origX)); 2097 assertThat(mOnUiThread.getScrollY(), greaterThan(origY)); 2098 } 2099 testSetDownloadListener()2100 public void testSetDownloadListener() throws Throwable { 2101 if (!NullWebViewUtils.isWebViewAvailable()) { 2102 return; 2103 } 2104 2105 final SettableFuture<Void> downloadStartFuture = SettableFuture.create(); 2106 final class MyDownloadListener implements DownloadListener { 2107 public String url; 2108 public String mimeType; 2109 public long contentLength; 2110 public String contentDisposition; 2111 2112 @Override 2113 public void onDownloadStart(String url, String userAgent, String contentDisposition, 2114 String mimetype, long contentLength) { 2115 this.url = url; 2116 this.mimeType = mimetype; 2117 this.contentLength = contentLength; 2118 this.contentDisposition = contentDisposition; 2119 downloadStartFuture.set(null); 2120 } 2121 } 2122 2123 final String mimeType = "application/octet-stream"; 2124 final int length = 100; 2125 final MyDownloadListener listener = new MyDownloadListener(); 2126 2127 startWebServer(false); 2128 final String url = mWebServer.getBinaryUrl(mimeType, length); 2129 2130 // By default, WebView sends an intent to ask the system to 2131 // handle loading a new URL. We set WebViewClient as 2132 // WebViewClient.shouldOverrideUrlLoading() returns false, so 2133 // the WebView will load the new URL. 2134 mOnUiThread.setDownloadListener(listener); 2135 mOnUiThread.getSettings().setJavaScriptEnabled(true); 2136 mOnUiThread.loadDataAndWaitForCompletion( 2137 "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>", 2138 "text/html", null); 2139 // Wait for layout to complete before setting focus. 2140 getInstrumentation().waitForIdleSync(); 2141 2142 WebkitUtils.waitForFuture(downloadStartFuture); 2143 assertEquals(url, listener.url); 2144 assertTrue(listener.contentDisposition.contains("test.bin")); 2145 assertEquals(length, listener.contentLength); 2146 assertEquals(mimeType, listener.mimeType); 2147 } 2148 2149 @UiThreadTest testSetLayoutParams()2150 public void testSetLayoutParams() { 2151 if (!NullWebViewUtils.isWebViewAvailable()) { 2152 return; 2153 } 2154 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800); 2155 mWebView.setLayoutParams(params); 2156 assertSame(params, mWebView.getLayoutParams()); 2157 } 2158 2159 @UiThreadTest testSetMapTrackballToArrowKeys()2160 public void testSetMapTrackballToArrowKeys() { 2161 if (!NullWebViewUtils.isWebViewAvailable()) { 2162 return; 2163 } 2164 mWebView.setMapTrackballToArrowKeys(true); 2165 } 2166 testSetNetworkAvailable()2167 public void testSetNetworkAvailable() throws Exception { 2168 if (!NullWebViewUtils.isWebViewAvailable()) { 2169 return; 2170 } 2171 WebSettings settings = mOnUiThread.getSettings(); 2172 settings.setJavaScriptEnabled(true); 2173 startWebServer(false); 2174 2175 String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL); 2176 mOnUiThread.loadUrlAndWaitForCompletion(url); 2177 assertEquals("ONLINE", mOnUiThread.getTitle()); 2178 2179 mOnUiThread.setNetworkAvailable(false); 2180 2181 // Wait for the DOM to receive notification of the network state change. 2182 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 2183 @Override 2184 protected boolean check() { 2185 return mOnUiThread.getTitle().equals("OFFLINE"); 2186 } 2187 }.run(); 2188 2189 mOnUiThread.setNetworkAvailable(true); 2190 2191 // Wait for the DOM to receive notification of the network state change. 2192 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 2193 @Override 2194 protected boolean check() { 2195 return mOnUiThread.getTitle().equals("ONLINE"); 2196 } 2197 }.run(); 2198 } 2199 testSetWebChromeClient()2200 public void testSetWebChromeClient() throws Throwable { 2201 if (!NullWebViewUtils.isWebViewAvailable()) { 2202 return; 2203 } 2204 2205 final SettableFuture<Void> future = SettableFuture.create(); 2206 mOnUiThread.setWebChromeClient(new WaitForProgressClient(mOnUiThread) { 2207 @Override 2208 public void onProgressChanged(WebView view, int newProgress) { 2209 super.onProgressChanged(view, newProgress); 2210 future.set(null); 2211 } 2212 }); 2213 getInstrumentation().waitForIdleSync(); 2214 assertFalse(future.isDone()); 2215 2216 startWebServer(false); 2217 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 2218 mOnUiThread.loadUrlAndWaitForCompletion(url); 2219 getInstrumentation().waitForIdleSync(); 2220 2221 WebkitUtils.waitForFuture(future); 2222 } 2223 testPauseResumeTimers()2224 public void testPauseResumeTimers() throws Throwable { 2225 if (!NullWebViewUtils.isWebViewAvailable()) { 2226 return; 2227 } 2228 class Monitor { 2229 private boolean mIsUpdated; 2230 2231 @JavascriptInterface 2232 public synchronized void update() { 2233 mIsUpdated = true; 2234 notify(); 2235 } 2236 public synchronized boolean waitForUpdate() { 2237 while (!mIsUpdated) { 2238 try { 2239 // This is slightly flaky, as we can't guarantee that 2240 // this is a sufficient time limit, but there's no way 2241 // around this. 2242 wait(1000); 2243 if (!mIsUpdated) { 2244 return false; 2245 } 2246 } catch (InterruptedException e) { 2247 } 2248 } 2249 mIsUpdated = false; 2250 return true; 2251 } 2252 }; 2253 final Monitor monitor = new Monitor(); 2254 final String updateMonitorHtml = "<html>" + 2255 "<body onload=\"monitor.update();\"></body></html>"; 2256 2257 // Test that JavaScript is executed even with timers paused. 2258 WebkitUtils.onMainThreadSync(() -> { 2259 mWebView.getSettings().setJavaScriptEnabled(true); 2260 mWebView.addJavascriptInterface(monitor, "monitor"); 2261 mWebView.pauseTimers(); 2262 mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml, 2263 "text/html", null); 2264 }); 2265 assertTrue(monitor.waitForUpdate()); 2266 2267 // Start a timer and test that it does not fire. 2268 mOnUiThread.loadDataAndWaitForCompletion( 2269 "<html><body onload='setTimeout(function(){monitor.update();},100)'>" + 2270 "</body></html>", "text/html", null); 2271 assertFalse(monitor.waitForUpdate()); 2272 2273 // Resume timers and test that the timer fires. 2274 mOnUiThread.resumeTimers(); 2275 assertTrue(monitor.waitForUpdate()); 2276 } 2277 2278 // verify query parameters can be passed correctly to android asset files testAndroidAssetQueryParam()2279 public void testAndroidAssetQueryParam() { 2280 if (!NullWebViewUtils.isWebViewAvailable()) { 2281 return; 2282 } 2283 2284 WebSettings settings = mOnUiThread.getSettings(); 2285 settings.setJavaScriptEnabled(true); 2286 // test passing a parameter 2287 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL+"?val=SUCCESS"); 2288 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 2289 assertEquals("SUCCESS", mOnUiThread.getTitle()); 2290 } 2291 2292 // verify anchors work correctly for android asset files testAndroidAssetAnchor()2293 public void testAndroidAssetAnchor() { 2294 if (!NullWebViewUtils.isWebViewAvailable()) { 2295 return; 2296 } 2297 2298 WebSettings settings = mOnUiThread.getSettings(); 2299 settings.setJavaScriptEnabled(true); 2300 // test using an anchor 2301 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL+"#anchor"); 2302 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 2303 assertEquals("anchor", mOnUiThread.getTitle()); 2304 } 2305 testEvaluateJavascript()2306 public void testEvaluateJavascript() { 2307 if (!NullWebViewUtils.isWebViewAvailable()) { 2308 return; 2309 } 2310 mOnUiThread.getSettings().setJavaScriptEnabled(true); 2311 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 2312 2313 assertEquals("2", mOnUiThread.evaluateJavascriptSync("1+1")); 2314 2315 assertEquals("9", mOnUiThread.evaluateJavascriptSync("1+1; 4+5")); 2316 2317 final String EXPECTED_TITLE = "test"; 2318 mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null); 2319 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 2320 @Override 2321 protected boolean check() { 2322 return mOnUiThread.getTitle().equals(EXPECTED_TITLE); 2323 } 2324 }.run(); 2325 } 2326 2327 // Verify Print feature can create a PDF file with a correct preamble. testPrinting()2328 public void testPrinting() throws Throwable { 2329 if (!NullWebViewUtils.isWebViewAvailable()) { 2330 return; 2331 } 2332 mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" + 2333 "<body>foo</body></html>", 2334 "text/html", null); 2335 final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter(); 2336 printDocumentStart(adapter); 2337 PrintAttributes attributes = new PrintAttributes.Builder() 2338 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 2339 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) 2340 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) 2341 .build(); 2342 final WebViewCtsActivity activity = getActivity(); 2343 final File file = activity.getFileStreamPath(PRINTER_TEST_FILE); 2344 final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, 2345 ParcelFileDescriptor.parseMode("w")); 2346 final SettableFuture<Void> result = SettableFuture.create(); 2347 printDocumentLayout(adapter, null, attributes, 2348 new LayoutResultCallback() { 2349 // Called on UI thread 2350 @Override 2351 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 2352 PageRange[] pageRanges = new PageRange[] {PageRange.ALL_PAGES}; 2353 savePrintedPage(adapter, descriptor, pageRanges, result); 2354 } 2355 }); 2356 try { 2357 WebkitUtils.waitForFuture(result); 2358 assertThat(file.length(), greaterThan(0L)); 2359 FileInputStream in = new FileInputStream(file); 2360 byte[] b = new byte[PDF_PREAMBLE.length()]; 2361 in.read(b); 2362 String preamble = new String(b); 2363 assertEquals(PDF_PREAMBLE, preamble); 2364 } finally { 2365 // close the descriptor, if not closed already. 2366 descriptor.close(); 2367 file.delete(); 2368 } 2369 } 2370 2371 // Verify Print feature can create a PDF file with correct number of pages. testPrintingPagesCount()2372 public void testPrintingPagesCount() throws Throwable { 2373 if (!NullWebViewUtils.isWebViewAvailable()) { 2374 return; 2375 } 2376 String content = "<html><head></head><body>"; 2377 for (int i = 0; i < 500; ++i) { 2378 content += "<br />abcdefghijk<br />"; 2379 } 2380 content += "</body></html>"; 2381 mOnUiThread.loadDataAndWaitForCompletion(content, "text/html", null); 2382 final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter(); 2383 printDocumentStart(adapter); 2384 PrintAttributes attributes = new PrintAttributes.Builder() 2385 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 2386 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) 2387 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) 2388 .build(); 2389 final WebViewCtsActivity activity = getActivity(); 2390 final File file = activity.getFileStreamPath(PRINTER_TEST_FILE); 2391 final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, 2392 ParcelFileDescriptor.parseMode("w")); 2393 final SettableFuture<Void> result = SettableFuture.create(); 2394 printDocumentLayout(adapter, null, attributes, 2395 new LayoutResultCallback() { 2396 // Called on UI thread 2397 @Override 2398 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 2399 PageRange[] pageRanges = new PageRange[] { 2400 new PageRange(1, 1), new PageRange(4, 7) 2401 }; 2402 savePrintedPage(adapter, descriptor, pageRanges, result); 2403 } 2404 }); 2405 try { 2406 WebkitUtils.waitForFuture(result); 2407 assertThat(file.length(), greaterThan(0L)); 2408 PdfRenderer renderer = new PdfRenderer( 2409 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)); 2410 assertEquals(5, renderer.getPageCount()); 2411 } finally { 2412 descriptor.close(); 2413 file.delete(); 2414 } 2415 } 2416 2417 /** 2418 * This should remain functionally equivalent to 2419 * androidx.webkit.WebViewCompatTest#testVisualStateCallbackCalled. Modifications to this test 2420 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2421 */ testVisualStateCallbackCalled()2422 public void testVisualStateCallbackCalled() throws Exception { 2423 // Check that the visual state callback is called correctly. 2424 if (!NullWebViewUtils.isWebViewAvailable()) { 2425 return; 2426 } 2427 2428 final long kRequest = 100; 2429 2430 mOnUiThread.loadUrl("about:blank"); 2431 2432 final SettableFuture<Long> visualStateFuture = SettableFuture.create(); 2433 mOnUiThread.postVisualStateCallback(kRequest, new VisualStateCallback() { 2434 public void onComplete(long requestId) { 2435 visualStateFuture.set(requestId); 2436 } 2437 }); 2438 2439 assertEquals(kRequest, (long) WebkitUtils.waitForFuture(visualStateFuture)); 2440 } 2441 setSafeBrowsingAllowlistSync(List<String> allowlist)2442 private static boolean setSafeBrowsingAllowlistSync(List<String> allowlist) { 2443 final SettableFuture<Boolean> safeBrowsingAllowlistFuture = SettableFuture.create(); 2444 WebView.setSafeBrowsingWhitelist(allowlist, new ValueCallback<Boolean>() { 2445 @Override 2446 public void onReceiveValue(Boolean success) { 2447 safeBrowsingAllowlistFuture.set(success); 2448 } 2449 }); 2450 return WebkitUtils.waitForFuture(safeBrowsingAllowlistFuture); 2451 } 2452 2453 /** 2454 * This should remain functionally equivalent to 2455 * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithMalformedList. 2456 * Modifications to this test should be reflected in that test as necessary. See 2457 * http://go/modifying-webview-cts. 2458 */ testSetSafeBrowsingAllowlistWithMalformedList()2459 public void testSetSafeBrowsingAllowlistWithMalformedList() throws Exception { 2460 if (!NullWebViewUtils.isWebViewAvailable()) { 2461 return; 2462 } 2463 2464 List allowlist = new ArrayList<String>(); 2465 // Protocols are not supported in the allowlist 2466 allowlist.add("http://google.com"); 2467 assertFalse("Malformed list entry should fail", setSafeBrowsingAllowlistSync(allowlist)); 2468 } 2469 2470 /** 2471 * This should remain functionally equivalent to 2472 * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithValidList. Modifications 2473 * to this test should be reflected in that test as necessary. See 2474 * http://go/modifying-webview-cts. 2475 */ testSetSafeBrowsingAllowlistWithValidList()2476 public void testSetSafeBrowsingAllowlistWithValidList() throws Exception { 2477 if (!NullWebViewUtils.isWebViewAvailable()) { 2478 return; 2479 } 2480 2481 List allowlist = new ArrayList<String>(); 2482 allowlist.add("safe-browsing"); 2483 assertTrue("Valid allowlist should be successful", setSafeBrowsingAllowlistSync(allowlist)); 2484 2485 final SettableFuture<Void> pageFinishedFuture = SettableFuture.create(); 2486 mOnUiThread.setWebViewClient(new WebViewClient() { 2487 @Override 2488 public void onPageFinished(WebView view, String url) { 2489 pageFinishedFuture.set(null); 2490 } 2491 2492 @Override 2493 public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, 2494 SafeBrowsingResponse callback) { 2495 pageFinishedFuture.setException(new IllegalStateException( 2496 "Should not invoke onSafeBrowsingHit for " + request.getUrl())); 2497 } 2498 }); 2499 2500 mOnUiThread.loadUrl("chrome://safe-browsing/match?type=malware"); 2501 2502 // Wait until page load has completed 2503 WebkitUtils.waitForFuture(pageFinishedFuture); 2504 } 2505 2506 /** 2507 * This should remain functionally equivalent to 2508 * androidx.webkit.WebViewCompatTest#testGetWebViewClient. Modifications to this test should be 2509 * reflected in that test as necessary. See http://go/modifying-webview-cts. 2510 */ 2511 @UiThreadTest testGetWebViewClient()2512 public void testGetWebViewClient() throws Exception { 2513 if (!NullWebViewUtils.isWebViewAvailable()) { 2514 return; 2515 } 2516 2517 // getWebViewClient should return a default WebViewClient if it hasn't been set yet 2518 WebView webView = new WebView(getActivity()); 2519 WebViewClient client = webView.getWebViewClient(); 2520 assertNotNull(client); 2521 assertTrue(client instanceof WebViewClient); 2522 2523 // getWebViewClient should return the client after it has been set 2524 WebViewClient client2 = new WebViewClient(); 2525 assertNotSame(client, client2); 2526 webView.setWebViewClient(client2); 2527 assertSame(client2, webView.getWebViewClient()); 2528 webView.destroy(); 2529 } 2530 2531 /** 2532 * This should remain functionally equivalent to 2533 * androidx.webkit.WebViewCompatTest#testGetWebChromeClient. Modifications to this test should 2534 * be reflected in that test as necessary. See http://go/modifying-webview-cts. 2535 */ 2536 @UiThreadTest testGetWebChromeClient()2537 public void testGetWebChromeClient() throws Exception { 2538 if (!NullWebViewUtils.isWebViewAvailable()) { 2539 return; 2540 } 2541 2542 // getWebChromeClient should return null if the client hasn't been set yet 2543 WebView webView = new WebView(getActivity()); 2544 WebChromeClient client = webView.getWebChromeClient(); 2545 assertNull(client); 2546 2547 // getWebChromeClient should return the client after it has been set 2548 WebChromeClient client2 = new WebChromeClient(); 2549 assertNotSame(client, client2); 2550 webView.setWebChromeClient(client2); 2551 assertSame(client2, webView.getWebChromeClient()); 2552 webView.destroy(); 2553 } 2554 2555 @UiThreadTest testSetCustomTextClassifier()2556 public void testSetCustomTextClassifier() throws Exception { 2557 if (!NullWebViewUtils.isWebViewAvailable()) { 2558 return; 2559 } 2560 2561 class CustomTextClassifier implements TextClassifier { 2562 @Override 2563 public TextSelection suggestSelection( 2564 CharSequence text, 2565 int startIndex, 2566 int endIndex, 2567 LocaleList defaultLocales) { 2568 return new TextSelection.Builder(0, 1).build(); 2569 } 2570 2571 @Override 2572 public TextClassification classifyText( 2573 CharSequence text, 2574 int startIndex, 2575 int endIndex, 2576 LocaleList defaultLocales) { 2577 return new TextClassification.Builder().build(); 2578 } 2579 }; 2580 2581 TextClassifier classifier = new CustomTextClassifier(); 2582 WebView webView = new WebView(getActivity()); 2583 webView.setTextClassifier(classifier); 2584 assertSame(webView.getTextClassifier(), classifier); 2585 webView.destroy(); 2586 } 2587 2588 private static class MockContext extends ContextWrapper { 2589 private boolean mGetApplicationContextWasCalled; 2590 MockContext(Context context)2591 public MockContext(Context context) { 2592 super(context); 2593 } 2594 getApplicationContext()2595 public Context getApplicationContext() { 2596 mGetApplicationContextWasCalled = true; 2597 return super.getApplicationContext(); 2598 } 2599 wasGetApplicationContextCalled()2600 public boolean wasGetApplicationContextCalled() { 2601 return mGetApplicationContextWasCalled; 2602 } 2603 } 2604 2605 /** 2606 * This should remain functionally equivalent to 2607 * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingUseApplicationContext. Modifications to 2608 * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2609 */ testStartSafeBrowsingUseApplicationContext()2610 public void testStartSafeBrowsingUseApplicationContext() throws Exception { 2611 if (!NullWebViewUtils.isWebViewAvailable()) { 2612 return; 2613 } 2614 2615 final MockContext ctx = new MockContext(getActivity()); 2616 final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create(); 2617 WebView.startSafeBrowsing(ctx, new ValueCallback<Boolean>() { 2618 @Override 2619 public void onReceiveValue(Boolean value) { 2620 startSafeBrowsingFuture.set(ctx.wasGetApplicationContextCalled()); 2621 return; 2622 } 2623 }); 2624 assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture)); 2625 } 2626 2627 /** 2628 * This should remain functionally equivalent to 2629 * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingWithNullCallbackDoesntCrash. 2630 * Modifications to this test should be reflected in that test as necessary. See 2631 * http://go/modifying-webview-cts. 2632 */ testStartSafeBrowsingWithNullCallbackDoesntCrash()2633 public void testStartSafeBrowsingWithNullCallbackDoesntCrash() throws Exception { 2634 if (!NullWebViewUtils.isWebViewAvailable()) { 2635 return; 2636 } 2637 2638 WebView.startSafeBrowsing(getActivity().getApplicationContext(), null); 2639 } 2640 2641 /** 2642 * This should remain functionally equivalent to 2643 * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingInvokesCallback. Modifications to 2644 * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2645 */ testStartSafeBrowsingInvokesCallback()2646 public void testStartSafeBrowsingInvokesCallback() throws Exception { 2647 if (!NullWebViewUtils.isWebViewAvailable()) { 2648 return; 2649 } 2650 2651 final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create(); 2652 WebView.startSafeBrowsing(getActivity().getApplicationContext(), 2653 new ValueCallback<Boolean>() { 2654 @Override 2655 public void onReceiveValue(Boolean value) { 2656 startSafeBrowsingFuture.set(Looper.getMainLooper().isCurrentThread()); 2657 return; 2658 } 2659 }); 2660 assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture)); 2661 } 2662 savePrintedPage(final PrintDocumentAdapter adapter, final ParcelFileDescriptor descriptor, final PageRange[] pageRanges, final SettableFuture<Void> result)2663 private void savePrintedPage(final PrintDocumentAdapter adapter, 2664 final ParcelFileDescriptor descriptor, final PageRange[] pageRanges, 2665 final SettableFuture<Void> result) { 2666 adapter.onWrite(pageRanges, descriptor, 2667 new CancellationSignal(), 2668 new WriteResultCallback() { 2669 @Override 2670 public void onWriteFinished(PageRange[] pages) { 2671 try { 2672 descriptor.close(); 2673 result.set(null); 2674 } catch (IOException ex) { 2675 result.setException(ex); 2676 } 2677 } 2678 }); 2679 } 2680 printDocumentStart(final PrintDocumentAdapter adapter)2681 private void printDocumentStart(final PrintDocumentAdapter adapter) { 2682 WebkitUtils.onMainThreadSync(() -> { 2683 adapter.onStart(); 2684 }); 2685 } 2686 printDocumentLayout(final PrintDocumentAdapter adapter, final PrintAttributes oldAttributes, final PrintAttributes newAttributes, final LayoutResultCallback layoutResultCallback)2687 private void printDocumentLayout(final PrintDocumentAdapter adapter, 2688 final PrintAttributes oldAttributes, final PrintAttributes newAttributes, 2689 final LayoutResultCallback layoutResultCallback) { 2690 WebkitUtils.onMainThreadSync(() -> { 2691 adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(), 2692 layoutResultCallback, null); 2693 }); 2694 } 2695 2696 private static class HrefCheckHandler extends Handler { 2697 private boolean mHadRecieved; 2698 2699 private String mResultUrl; 2700 HrefCheckHandler(Looper looper)2701 public HrefCheckHandler(Looper looper) { 2702 super(looper); 2703 } 2704 hasCalledHandleMessage()2705 public boolean hasCalledHandleMessage() { 2706 return mHadRecieved; 2707 } 2708 getResultUrl()2709 public String getResultUrl() { 2710 return mResultUrl; 2711 } 2712 reset()2713 public void reset(){ 2714 mResultUrl = null; 2715 mHadRecieved = false; 2716 } 2717 2718 @Override handleMessage(Message msg)2719 public void handleMessage(Message msg) { 2720 mResultUrl = msg.getData().getString("url"); 2721 mHadRecieved = true; 2722 } 2723 } 2724 moveFocusDown()2725 private void moveFocusDown() throws Throwable { 2726 // send down key and wait for idle 2727 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 2728 // waiting for idle isn't always sufficient for the key to be fully processed 2729 Thread.sleep(500); 2730 } 2731 pollingCheckWebBackForwardList(final String currUrl, final int currIndex, final int size)2732 private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex, 2733 final int size) { 2734 new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) { 2735 @Override 2736 protected boolean check() { 2737 WebBackForwardList list = mWebView.copyBackForwardList(); 2738 return checkWebBackForwardList(list, currUrl, currIndex, size); 2739 } 2740 }.run(); 2741 } 2742 checkWebBackForwardList(WebBackForwardList list, String currUrl, int currIndex, int size)2743 private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl, 2744 int currIndex, int size) { 2745 return (list != null) 2746 && (list.getSize() == size) 2747 && (list.getCurrentIndex() == currIndex) 2748 && list.getItemAtIndex(currIndex).getUrl().equals(currUrl); 2749 } 2750 assertGoBackOrForwardBySteps(boolean expected, int steps)2751 private void assertGoBackOrForwardBySteps(boolean expected, int steps) { 2752 // skip if steps equals to 0 2753 if (steps == 0) 2754 return; 2755 2756 int start = steps > 0 ? 1 : steps; 2757 int end = steps > 0 ? steps : -1; 2758 2759 // check all the steps in the history 2760 for (int i = start; i <= end; i++) { 2761 assertEquals(expected, mWebView.canGoBackOrForward(i)); 2762 2763 // shortcut methods for one step 2764 if (i == 1) { 2765 assertEquals(expected, mWebView.canGoForward()); 2766 } else if (i == -1) { 2767 assertEquals(expected, mWebView.canGoBack()); 2768 } 2769 } 2770 } 2771 isPictureFilledWithColor(Picture picture, int color)2772 private boolean isPictureFilledWithColor(Picture picture, int color) { 2773 if (picture.getWidth() == 0 || picture.getHeight() == 0) 2774 return false; 2775 2776 Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), 2777 Config.ARGB_8888); 2778 picture.draw(new Canvas(bitmap)); 2779 2780 for (int i = 0; i < bitmap.getWidth(); i ++) { 2781 for (int j = 0; j < bitmap.getHeight(); j ++) { 2782 if (color != bitmap.getPixel(i, j)) { 2783 return false; 2784 } 2785 } 2786 } 2787 return true; 2788 } 2789 2790 /** 2791 * Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started, 2792 * scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once 2793 * changes have stopped, the function exits. If no scrolling has happened 2794 * then the function exits after MIN_SCROLL_WAIT milliseconds. 2795 * @param previousScrollY The Y scroll position prior to waiting. 2796 */ waitForScrollingComplete(int previousScrollY)2797 private void waitForScrollingComplete(int previousScrollY) 2798 throws InterruptedException { 2799 int scrollY = previousScrollY; 2800 // wait at least MIN_SCROLL_WAIT for something to happen. 2801 long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS; 2802 boolean scrollChanging = false; 2803 boolean scrollChanged = false; 2804 boolean minWaitExpired = false; 2805 while (scrollChanging || (!scrollChanged && !minWaitExpired)) { 2806 Thread.sleep(SCROLL_WAIT_INTERVAL_MS); 2807 int oldScrollY = scrollY; 2808 scrollY = mOnUiThread.getScrollY(); 2809 scrollChanging = (scrollY != oldScrollY); 2810 scrollChanged = (scrollY != previousScrollY); 2811 minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait); 2812 } 2813 } 2814 2815 /** 2816 * This should remain functionally equivalent to 2817 * androidx.webkit.WebViewCompatTest#testGetSafeBrowsingPrivacyPolicyUrl. Modifications to this 2818 * test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2819 */ testGetSafeBrowsingPrivacyPolicyUrl()2820 public void testGetSafeBrowsingPrivacyPolicyUrl() throws Exception { 2821 if (!NullWebViewUtils.isWebViewAvailable()) { 2822 return; 2823 } 2824 2825 assertNotNull(WebView.getSafeBrowsingPrivacyPolicyUrl()); 2826 try { 2827 new URL(WebView.getSafeBrowsingPrivacyPolicyUrl().toString()); 2828 } catch (MalformedURLException e) { 2829 fail("The privacy policy URL should be a well-formed URL"); 2830 } 2831 } 2832 testWebViewClassLoaderReturnsNonNull()2833 public void testWebViewClassLoaderReturnsNonNull() { 2834 if (!NullWebViewUtils.isWebViewAvailable()) { 2835 return; 2836 } 2837 2838 assertNotNull(WebView.getWebViewClassLoader()); 2839 } 2840 } 2841