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