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.content.res.AssetManager; 27 import android.graphics.Bitmap; 28 import android.graphics.Bitmap.Config; 29 import android.graphics.BitmapFactory; 30 import android.graphics.Canvas; 31 import android.graphics.Color; 32 import android.graphics.Picture; 33 import android.graphics.Rect; 34 import android.graphics.pdf.PdfRenderer; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.CancellationSignal; 38 import android.os.Handler; 39 import android.os.LocaleList; 40 import android.os.Looper; 41 import android.os.Message; 42 import android.os.ParcelFileDescriptor; 43 import android.os.StrictMode; 44 import android.os.StrictMode.ThreadPolicy; 45 import android.os.SystemClock; 46 import android.platform.test.annotations.AppModeFull; 47 import android.platform.test.annotations.Presubmit; 48 import android.print.PageRange; 49 import android.print.PrintAttributes; 50 import android.print.PrintDocumentAdapter; 51 import android.print.PrintDocumentAdapter.LayoutResultCallback; 52 import android.print.PrintDocumentAdapter.WriteResultCallback; 53 import android.print.PrintDocumentInfo; 54 import android.test.ActivityInstrumentationTestCase2; 55 import android.test.UiThreadTest; 56 import android.util.AttributeSet; 57 import android.util.DisplayMetrics; 58 import android.view.KeyEvent; 59 import android.view.MotionEvent; 60 import android.view.View; 61 import android.view.ViewGroup; 62 import android.view.textclassifier.TextClassification; 63 import android.view.textclassifier.TextClassifier; 64 import android.view.textclassifier.TextSelection; 65 import android.webkit.ConsoleMessage; 66 import android.webkit.CookieSyncManager; 67 import android.webkit.DownloadListener; 68 import android.webkit.JavascriptInterface; 69 import android.webkit.SafeBrowsingResponse; 70 import android.webkit.ValueCallback; 71 import android.webkit.WebBackForwardList; 72 import android.webkit.WebChromeClient; 73 import android.webkit.WebIconDatabase; 74 import android.webkit.WebResourceRequest; 75 import android.webkit.WebSettings; 76 import android.webkit.WebView; 77 import android.webkit.WebView.HitTestResult; 78 import android.webkit.WebView.PictureListener; 79 import android.webkit.WebView.VisualStateCallback; 80 import android.webkit.WebViewClient; 81 import android.webkit.WebViewDatabase; 82 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient; 83 import android.webkit.cts.WebViewSyncLoader.WaitForProgressClient; 84 import android.widget.LinearLayout; 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 DummyJavaScriptInterface { 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 DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); 636 mOnUiThread.addJavascriptInterface(obj, "dummy"); 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 dummy.provideResult")); 648 649 assertEquals("\"undefined\"", 650 mOnUiThread.evaluateJavascriptSync("typeof dummy.wasProvideResultCalled")); 651 652 assertEquals("\"undefined\"", 653 mOnUiThread.evaluateJavascriptSync("typeof dummy.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, "dummy"); 783 784 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 785 786 assertFalse(mJsInterfaceWasCalled.get()); 787 788 assertEquals("\"pass\"", mOnUiThread.evaluateJavascriptSync( 789 "try {dummy.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 class DummyJavaScriptInterface { 801 } 802 final DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); 803 mOnUiThread.addJavascriptInterface(obj, "dummy"); 804 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 805 806 assertEquals("42", mOnUiThread.evaluateJavascriptSync("dummy.custom_property = 42")); 807 808 assertEquals("true", mOnUiThread.evaluateJavascriptSync("'custom_property' in dummy")); 809 810 mOnUiThread.reloadAndWaitForCompletion(); 811 812 assertEquals("false", mOnUiThread.evaluateJavascriptSync("'custom_property' in dummy")); 813 } 814 testJavascriptInterfaceForClientPopup()815 public void testJavascriptInterfaceForClientPopup() throws Exception { 816 if (!NullWebViewUtils.isWebViewAvailable()) { 817 return; 818 } 819 820 mOnUiThread.getSettings().setJavaScriptEnabled(true); 821 mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); 822 mOnUiThread.getSettings().setSupportMultipleWindows(true); 823 824 class DummyJavaScriptInterface { 825 @JavascriptInterface 826 public int test() { 827 return 42; 828 } 829 } 830 final DummyJavaScriptInterface obj = new DummyJavaScriptInterface(); 831 832 final WebView childWebView = mOnUiThread.createWebView(); 833 WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(childWebView); 834 childOnUiThread.getSettings().setJavaScriptEnabled(true); 835 childOnUiThread.addJavascriptInterface(obj, "dummy"); 836 837 final SettableFuture<Void> onCreateWindowFuture = SettableFuture.create(); 838 mOnUiThread.setWebChromeClient(new WebViewSyncLoader.WaitForProgressClient(mOnUiThread) { 839 @Override 840 public boolean onCreateWindow( 841 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 842 getActivity().addContentView(childWebView, new ViewGroup.LayoutParams( 843 ViewGroup.LayoutParams.FILL_PARENT, 844 ViewGroup.LayoutParams.WRAP_CONTENT)); 845 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; 846 transport.setWebView(childWebView); 847 resultMsg.sendToTarget(); 848 onCreateWindowFuture.set(null); 849 return true; 850 } 851 }); 852 853 startWebServer(false); 854 mOnUiThread.loadUrlAndWaitForCompletion(mWebServer. 855 getAssetUrl(TestHtmlConstants.POPUP_URL)); 856 WebkitUtils.waitForFuture(onCreateWindowFuture); 857 858 childOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 859 860 assertEquals("true", childOnUiThread.evaluateJavascriptSync("'dummy' in window")); 861 862 assertEquals("The injected object should be functional", "42", 863 childOnUiThread.evaluateJavascriptSync("dummy.test()")); 864 } 865 866 private final class TestPictureListener implements PictureListener { 867 public int callCount; 868 869 @Override onNewPicture(WebView view, Picture picture)870 public void onNewPicture(WebView view, Picture picture) { 871 // Need to inform the listener tracking new picture 872 // for the "page loaded" knowledge since it has been replaced. 873 mOnUiThread.onNewPicture(); 874 this.callCount += 1; 875 } 876 } 877 waitForPictureToHaveColor(int color, final TestPictureListener listener)878 private Picture waitForPictureToHaveColor(int color, 879 final TestPictureListener listener) throws Throwable { 880 final int MAX_ON_NEW_PICTURE_ITERATIONS = 5; 881 final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>(); 882 for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) { 883 final int oldCallCount = listener.callCount; 884 WebkitUtils.onMainThreadSync(() -> { 885 pictureRef.set(mWebView.capturePicture()); 886 }); 887 if (isPictureFilledWithColor(pictureRef.get(), color)) 888 break; 889 new PollingCheck(TEST_TIMEOUT) { 890 @Override 891 protected boolean check() { 892 return listener.callCount > oldCallCount; 893 } 894 }.run(); 895 } 896 return pictureRef.get(); 897 } 898 testCapturePicture()899 public void testCapturePicture() throws Exception, Throwable { 900 if (!NullWebViewUtils.isWebViewAvailable()) { 901 return; 902 } 903 final TestPictureListener listener = new TestPictureListener(); 904 905 startWebServer(false); 906 final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL); 907 mOnUiThread.setPictureListener(listener); 908 // Showing the blank page will fill the picture with the background color. 909 mOnUiThread.loadUrlAndWaitForCompletion(url); 910 // The default background color is white. 911 Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener); 912 913 WebkitUtils.onMainThread(() -> { 914 mWebView.setBackgroundColor(Color.CYAN); 915 }); 916 mOnUiThread.reloadAndWaitForCompletion(); 917 waitForPictureToHaveColor(Color.CYAN, listener); 918 919 assertTrue("The content of the previously captured picture should not update automatically", 920 isPictureFilledWithColor(oldPicture, Color.WHITE)); 921 } 922 testSetPictureListener()923 public void testSetPictureListener() throws Exception, Throwable { 924 if (!NullWebViewUtils.isWebViewAvailable()) { 925 return; 926 } 927 final class MyPictureListener implements PictureListener { 928 public int callCount; 929 public WebView webView; 930 public Picture picture; 931 932 @Override 933 public void onNewPicture(WebView view, Picture picture) { 934 // Need to inform the listener tracking new picture 935 // for the "page loaded" knowledge since it has been replaced. 936 mOnUiThread.onNewPicture(); 937 this.callCount += 1; 938 this.webView = view; 939 this.picture = picture; 940 } 941 } 942 943 final MyPictureListener listener = new MyPictureListener(); 944 startWebServer(false); 945 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 946 mOnUiThread.setPictureListener(listener); 947 mOnUiThread.loadUrlAndWaitForCompletion(url); 948 new PollingCheck(TEST_TIMEOUT) { 949 @Override 950 protected boolean check() { 951 return listener.callCount > 0; 952 } 953 }.run(); 954 assertEquals(mWebView, listener.webView); 955 assertNull(listener.picture); 956 957 final int oldCallCount = listener.callCount; 958 final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); 959 mOnUiThread.loadUrlAndWaitForCompletion(newUrl); 960 new PollingCheck(TEST_TIMEOUT) { 961 @Override 962 protected boolean check() { 963 return listener.callCount > oldCallCount; 964 } 965 }.run(); 966 } 967 968 @UiThreadTest testAccessHttpAuthUsernamePassword()969 public void testAccessHttpAuthUsernamePassword() { 970 if (!NullWebViewUtils.isWebViewAvailable()) { 971 return; 972 } 973 try { 974 WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); 975 976 String host = "http://localhost:8080"; 977 String realm = "testrealm"; 978 String userName = "user"; 979 String password = "password"; 980 981 String[] result = mWebView.getHttpAuthUsernamePassword(host, realm); 982 assertNull(result); 983 984 mWebView.setHttpAuthUsernamePassword(host, realm, userName, password); 985 result = mWebView.getHttpAuthUsernamePassword(host, realm); 986 assertNotNull(result); 987 assertEquals(userName, result[0]); 988 assertEquals(password, result[1]); 989 990 String newPassword = "newpassword"; 991 mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword); 992 result = mWebView.getHttpAuthUsernamePassword(host, realm); 993 assertNotNull(result); 994 assertEquals(userName, result[0]); 995 assertEquals(newPassword, result[1]); 996 997 String newUserName = "newuser"; 998 mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 999 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1000 assertNotNull(result); 1001 assertEquals(newUserName, result[0]); 1002 assertEquals(newPassword, result[1]); 1003 1004 // the user is set to null, can not change any thing in the future 1005 mWebView.setHttpAuthUsernamePassword(host, realm, null, password); 1006 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1007 assertNotNull(result); 1008 assertNull(result[0]); 1009 assertEquals(password, result[1]); 1010 1011 mWebView.setHttpAuthUsernamePassword(host, realm, userName, null); 1012 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1013 assertNotNull(result); 1014 assertEquals(userName, result[0]); 1015 assertNull(result[1]); 1016 1017 mWebView.setHttpAuthUsernamePassword(host, realm, null, null); 1018 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1019 assertNotNull(result); 1020 assertNull(result[0]); 1021 assertNull(result[1]); 1022 1023 mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 1024 result = mWebView.getHttpAuthUsernamePassword(host, realm); 1025 assertNotNull(result); 1026 assertEquals(newUserName, result[0]); 1027 assertEquals(newPassword, result[1]); 1028 } finally { 1029 WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword(); 1030 } 1031 } 1032 1033 @UiThreadTest testWebViewDatabaseAccessHttpAuthUsernamePassword()1034 public void testWebViewDatabaseAccessHttpAuthUsernamePassword() { 1035 if (!NullWebViewUtils.isWebViewAvailable()) { 1036 return; 1037 } 1038 WebViewDatabase webViewDb = WebViewDatabase.getInstance(getActivity()); 1039 try { 1040 webViewDb.clearHttpAuthUsernamePassword(); 1041 1042 String host = "http://localhost:8080"; 1043 String realm = "testrealm"; 1044 String userName = "user"; 1045 String password = "password"; 1046 1047 String[] result = 1048 mWebView.getHttpAuthUsernamePassword(host, 1049 realm); 1050 assertNull(result); 1051 1052 webViewDb.setHttpAuthUsernamePassword(host, realm, userName, password); 1053 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1054 assertNotNull(result); 1055 assertEquals(userName, result[0]); 1056 assertEquals(password, result[1]); 1057 1058 String newPassword = "newpassword"; 1059 webViewDb.setHttpAuthUsernamePassword(host, realm, userName, newPassword); 1060 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1061 assertNotNull(result); 1062 assertEquals(userName, result[0]); 1063 assertEquals(newPassword, result[1]); 1064 1065 String newUserName = "newuser"; 1066 webViewDb.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 1067 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1068 assertNotNull(result); 1069 assertEquals(newUserName, result[0]); 1070 assertEquals(newPassword, result[1]); 1071 1072 // the user is set to null, can not change any thing in the future 1073 webViewDb.setHttpAuthUsernamePassword(host, realm, null, password); 1074 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1075 assertNotNull(result); 1076 assertNull(result[0]); 1077 assertEquals(password, result[1]); 1078 1079 webViewDb.setHttpAuthUsernamePassword(host, realm, userName, null); 1080 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1081 assertNotNull(result); 1082 assertEquals(userName, result[0]); 1083 assertNull(result[1]); 1084 1085 webViewDb.setHttpAuthUsernamePassword(host, realm, null, null); 1086 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1087 assertNotNull(result); 1088 assertNull(result[0]); 1089 assertNull(result[1]); 1090 1091 webViewDb.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword); 1092 result = webViewDb.getHttpAuthUsernamePassword(host, realm); 1093 assertNotNull(result); 1094 assertEquals(newUserName, result[0]); 1095 assertEquals(newPassword, result[1]); 1096 } finally { 1097 webViewDb.clearHttpAuthUsernamePassword(); 1098 } 1099 } 1100 testLoadData()1101 public void testLoadData() throws Throwable { 1102 if (!NullWebViewUtils.isWebViewAvailable()) { 1103 return; 1104 } 1105 final String firstTitle = "Hello, World!"; 1106 final String HTML_CONTENT = 1107 "<html><head><title>" + firstTitle + "</title></head><body></body>" + 1108 "</html>"; 1109 mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT, 1110 "text/html", null); 1111 assertEquals(firstTitle, mOnUiThread.getTitle()); 1112 1113 startWebServer(false); 1114 final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1115 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1116 final String secondTitle = "Foo bar"; 1117 mOnUiThread.loadDataAndWaitForCompletion( 1118 "<html><head><title>" + secondTitle + "</title></head><body onload=\"" + 1119 "document.title = " + 1120 "document.getElementById('frame').contentWindow.location.href;" + 1121 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", 1122 "text/html", null); 1123 assertEquals("Page title should not change, because it should be an error to access a " 1124 + "cross-site frame's href.", 1125 secondTitle, mOnUiThread.getTitle()); 1126 } 1127 testLoadDataWithBaseUrl_resolvesRelativeToBaseUrl()1128 public void testLoadDataWithBaseUrl_resolvesRelativeToBaseUrl() throws Throwable { 1129 if (!NullWebViewUtils.isWebViewAvailable()) { 1130 return; 1131 } 1132 assertNull(mOnUiThread.getUrl()); 1133 String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative 1134 1135 // Trying to resolve a relative URL against a data URL without a base URL 1136 // will fail and we won't make a request to the test web server. 1137 // By using the test web server as the base URL we expect to see a request 1138 // for the relative URL in the test server. 1139 startWebServer(false); 1140 final String baseUrl = mWebServer.getAssetUrl("foo.html"); 1141 mWebServer.resetRequestState(); 1142 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1143 HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>", 1144 "text/html", "UTF-8", null); 1145 assertTrue("The resource request should make it to the server", 1146 mWebServer.wasResourceRequested(imgUrl)); 1147 } 1148 testLoadDataWithBaseUrl_historyUrl()1149 public void testLoadDataWithBaseUrl_historyUrl() throws Throwable { 1150 if (!NullWebViewUtils.isWebViewAvailable()) { 1151 return; 1152 } 1153 final String baseUrl = "http://www.baseurl.com/"; 1154 final String historyUrl = "http://www.example.com/"; 1155 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1156 SIMPLE_HTML, 1157 "text/html", "UTF-8", historyUrl); 1158 assertEquals(historyUrl, mOnUiThread.getUrl()); 1159 } 1160 testLoadDataWithBaseUrl_nullHistoryUrlShowsAsAboutBlank()1161 public void testLoadDataWithBaseUrl_nullHistoryUrlShowsAsAboutBlank() throws Throwable { 1162 if (!NullWebViewUtils.isWebViewAvailable()) { 1163 return; 1164 } 1165 // Check that reported URL is "about:blank" when supplied history URL 1166 // is null. 1167 final String baseUrl = "http://www.baseurl.com/"; 1168 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1169 SIMPLE_HTML, 1170 "text/html", "UTF-8", null); 1171 assertEquals("about:blank", mOnUiThread.getUrl()); 1172 } 1173 testLoadDataWithBaseUrl_javascriptCanAccessOrigin()1174 public void testLoadDataWithBaseUrl_javascriptCanAccessOrigin() throws Throwable { 1175 if (!NullWebViewUtils.isWebViewAvailable()) { 1176 return; 1177 } 1178 // Test that JavaScript can access content from the same origin as the base URL. 1179 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1180 startWebServer(false); 1181 final String baseUrl = mWebServer.getAssetUrl("foo.html"); 1182 final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 1183 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1184 HTML_HEADER + "<body onload=\"" + 1185 "document.title = document.getElementById('frame').contentWindow.location.href;" + 1186 "\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>", 1187 "text/html", "UTF-8", null); 1188 assertEquals(crossOriginUrl, mOnUiThread.getTitle()); 1189 } 1190 testLoadDataWithBaseUrl_dataBaseUrlIgnoresHistoryUrl()1191 public void testLoadDataWithBaseUrl_dataBaseUrlIgnoresHistoryUrl() throws Throwable { 1192 if (!NullWebViewUtils.isWebViewAvailable()) { 1193 return; 1194 } 1195 // Check that when the base URL uses the 'data' scheme, a 'data' scheme URL is used and the 1196 // history URL is ignored. 1197 final String baseUrl = "data:foo"; 1198 final String historyUrl = "http://www.example.com/"; 1199 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl, 1200 SIMPLE_HTML, 1201 "text/html", "UTF-8", historyUrl); 1202 1203 final String currentUrl = mOnUiThread.getUrl(); 1204 assertEquals("Current URL (" + currentUrl + ") should be a data URI", 0, 1205 mOnUiThread.getUrl().indexOf("data:text/html")); 1206 assertThat("Current URL (" + currentUrl + ") should contain the simple HTML we loaded", 1207 mOnUiThread.getUrl().indexOf("simple html"), greaterThan(0)); 1208 } 1209 testLoadDataWithBaseUrl_unencodedContentHttpBaseUrl()1210 public void testLoadDataWithBaseUrl_unencodedContentHttpBaseUrl() throws Throwable { 1211 if (!NullWebViewUtils.isWebViewAvailable()) { 1212 return; 1213 } 1214 // Check that when a non-data: base URL is used, we treat the String to load as 1215 // a raw string and just dump it into the WebView, i.e. not decoding any URL entities. 1216 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com", 1217 HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>", 1218 "text/html", "UTF-8", null); 1219 assertEquals("Hello World%21", mOnUiThread.getTitle()); 1220 } 1221 testLoadDataWithBaseUrl_urlEncodedContentDataBaseUrl()1222 public void testLoadDataWithBaseUrl_urlEncodedContentDataBaseUrl() throws Throwable { 1223 if (!NullWebViewUtils.isWebViewAvailable()) { 1224 return; 1225 } 1226 // Check that when a data: base URL is used, we treat the String to load as a data: URL 1227 // and run load steps such as decoding URL entities (i.e., contrary to the test case 1228 // above.) 1229 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo", 1230 HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null); 1231 assertEquals("Hello World!", mOnUiThread.getTitle()); 1232 } 1233 testLoadDataWithBaseUrl_nullSafe()1234 public void testLoadDataWithBaseUrl_nullSafe() throws Throwable { 1235 if (!NullWebViewUtils.isWebViewAvailable()) { 1236 return; 1237 } 1238 1239 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null); 1240 assertEquals("about:blank", mOnUiThread.getUrl()); 1241 } 1242 deleteIfExists(File file)1243 private void deleteIfExists(File file) throws IOException { 1244 if (file.exists()) { 1245 file.delete(); 1246 } 1247 } 1248 readTextFile(File file, Charset encoding)1249 private String readTextFile(File file, Charset encoding) 1250 throws FileNotFoundException, IOException { 1251 FileInputStream stream = new FileInputStream(file); 1252 byte[] bytes = new byte[(int)file.length()]; 1253 stream.read(bytes); 1254 stream.close(); 1255 return new String(bytes, encoding); 1256 } 1257 doSaveWebArchive(String baseName, boolean autoName, final String expectName)1258 private void doSaveWebArchive(String baseName, boolean autoName, final String expectName) 1259 throws Throwable { 1260 final Semaphore saving = new Semaphore(0); 1261 ValueCallback<String> callback = new ValueCallback<String>() { 1262 @Override 1263 public void onReceiveValue(String savedName) { 1264 assertEquals(expectName, savedName); 1265 saving.release(); 1266 } 1267 }; 1268 1269 mOnUiThread.saveWebArchive(baseName, autoName, callback); 1270 assertTrue(saving.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS)); 1271 } 1272 testSaveWebArchive()1273 public void testSaveWebArchive() throws Throwable { 1274 if (!NullWebViewUtils.isWebViewAvailable()) { 1275 return; 1276 } 1277 1278 final String testPage = "testSaveWebArchive test page"; 1279 1280 File dir = getActivity().getFilesDir(); 1281 String dirStr = dir.toString(); 1282 1283 File test = new File(dir, "test.mht"); 1284 deleteIfExists(test); 1285 String testStr = test.getAbsolutePath(); 1286 1287 File index = new File(dir, "index.mht"); 1288 deleteIfExists(index); 1289 String indexStr = index.getAbsolutePath(); 1290 1291 File index1 = new File(dir, "index-1.mht"); 1292 deleteIfExists(index1); 1293 String index1Str = index1.getAbsolutePath(); 1294 1295 mOnUiThread.loadDataAndWaitForCompletion(testPage, "text/html", "UTF-8"); 1296 1297 try { 1298 // Save test.mht 1299 doSaveWebArchive(testStr, false, testStr); 1300 1301 // Check the contents of test.mht 1302 String testMhtml = readTextFile(test, StandardCharsets.UTF_8); 1303 assertTrue(testMhtml.contains(testPage)); 1304 1305 // Save index.mht 1306 doSaveWebArchive(dirStr + "/", true, indexStr); 1307 1308 // Check the contents of index.mht 1309 String indexMhtml = readTextFile(index, StandardCharsets.UTF_8); 1310 assertTrue(indexMhtml.contains(testPage)); 1311 1312 // Save index-1.mht since index.mht already exists 1313 doSaveWebArchive(dirStr + "/", true, index1Str); 1314 1315 // Check the contents of index-1.mht 1316 String index1Mhtml = readTextFile(index1, StandardCharsets.UTF_8); 1317 assertTrue(index1Mhtml.contains(testPage)); 1318 1319 // Try a file in a bogus directory 1320 doSaveWebArchive("/bogus/path/test.mht", false, null); 1321 1322 // Try a bogus directory 1323 doSaveWebArchive("/bogus/path/", true, null); 1324 } finally { 1325 deleteIfExists(test); 1326 deleteIfExists(index); 1327 deleteIfExists(index1); 1328 } 1329 } 1330 1331 private static class WaitForFindResultsListener 1332 implements WebView.FindListener { 1333 private final SettableFuture<Integer> mFuture; 1334 private final WebView mWebView; 1335 private final int mMatchesWanted; 1336 private final String mStringWanted; 1337 private final boolean mRetry; 1338 WaitForFindResultsListener( WebView wv, String wanted, int matches, boolean retry)1339 public WaitForFindResultsListener( 1340 WebView wv, String wanted, int matches, boolean retry) { 1341 mFuture = SettableFuture.create(); 1342 mWebView = wv; 1343 mMatchesWanted = matches; 1344 mStringWanted = wanted; 1345 mRetry = retry; 1346 } 1347 future()1348 public Future<Integer> future() { 1349 return mFuture; 1350 } 1351 1352 @Override onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, boolean isDoneCounting)1353 public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches, 1354 boolean isDoneCounting) { 1355 try { 1356 assertEquals("WebView.FindListener callbacks should occur on the UI thread", 1357 Looper.myLooper(), Looper.getMainLooper()); 1358 } catch (Throwable t) { 1359 mFuture.setException(t); 1360 } 1361 if (isDoneCounting) { 1362 //If mRetry set to true and matches aren't equal, call findAll again 1363 if (mRetry && numberOfMatches != mMatchesWanted) { 1364 mWebView.findAll(mStringWanted); 1365 } 1366 else { 1367 mFuture.set(numberOfMatches); 1368 } 1369 } 1370 } 1371 } 1372 testFindAll()1373 public void testFindAll() throws Throwable { 1374 if (!NullWebViewUtils.isWebViewAvailable()) { 1375 return; 1376 } 1377 // Make the page scrollable, so we can detect the scrolling to make sure the 1378 // content fully loaded. 1379 mOnUiThread.setInitialScale(100); 1380 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1381 int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); 1382 // create a paragraph high enough to take up the entire screen 1383 String p = "<p style=\"height:" + dimension + "px;\">" + 1384 "Find all instances of find on the page and highlight them.</p>"; 1385 1386 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1387 + "</body></html>", "text/html", null); 1388 1389 WaitForFindResultsListener l = new WaitForFindResultsListener(mWebView, "find", 2, true); 1390 mOnUiThread.setFindListener(l); 1391 mOnUiThread.findAll("find"); 1392 assertEquals(2, (int)WebkitUtils.waitForFuture(l.future())); 1393 } 1394 testFindNext()1395 public void testFindNext() throws Throwable { 1396 if (!NullWebViewUtils.isWebViewAvailable()) { 1397 return; 1398 } 1399 // Reset the scaling so that finding the next "all" text will require scrolling. 1400 mOnUiThread.setInitialScale(100); 1401 1402 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1403 int dimension = Math.max(metrics.widthPixels, metrics.heightPixels); 1404 // create a paragraph high enough to take up the entire screen 1405 String p = "<p style=\"height:" + dimension + "px;\">" + 1406 "Find all instances of a word on the page and highlight them.</p>"; 1407 1408 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null); 1409 WaitForFindResultsListener l = new WaitForFindResultsListener(mWebView, "all", 2, true); 1410 mOnUiThread.setFindListener(l); 1411 1412 // highlight all the strings found and wait for all the matches to be found 1413 mOnUiThread.findAll("all"); 1414 WebkitUtils.waitForFuture(l.future()); 1415 mOnUiThread.setFindListener(null); 1416 1417 int previousScrollY = mOnUiThread.getScrollY(); 1418 1419 // Focus "all" in the second page and assert that the view scrolls. 1420 mOnUiThread.findNext(true); 1421 waitForScrollingComplete(previousScrollY); 1422 assertThat(mOnUiThread.getScrollY(), greaterThan(previousScrollY)); 1423 previousScrollY = mOnUiThread.getScrollY(); 1424 1425 // Focus "all" in the first page and assert that the view scrolls. 1426 mOnUiThread.findNext(true); 1427 waitForScrollingComplete(previousScrollY); 1428 assertThat(mOnUiThread.getScrollY(), lessThan(previousScrollY)); 1429 previousScrollY = mOnUiThread.getScrollY(); 1430 1431 // Focus "all" in the second page and assert that the view scrolls. 1432 mOnUiThread.findNext(false); 1433 waitForScrollingComplete(previousScrollY); 1434 assertThat(mOnUiThread.getScrollY(), greaterThan(previousScrollY)); 1435 previousScrollY = mOnUiThread.getScrollY(); 1436 1437 // Focus "all" in the first page and assert that the view scrolls. 1438 mOnUiThread.findNext(false); 1439 waitForScrollingComplete(previousScrollY); 1440 assertThat(mOnUiThread.getScrollY(), lessThan(previousScrollY)); 1441 previousScrollY = mOnUiThread.getScrollY(); 1442 1443 // clear the result 1444 mOnUiThread.clearMatches(); 1445 getInstrumentation().waitForIdleSync(); 1446 1447 // can not scroll any more 1448 mOnUiThread.findNext(false); 1449 waitForScrollingComplete(previousScrollY); 1450 assertTrue(mOnUiThread.getScrollY() == previousScrollY); 1451 1452 mOnUiThread.findNext(true); 1453 waitForScrollingComplete(previousScrollY); 1454 assertTrue(mOnUiThread.getScrollY() == previousScrollY); 1455 } 1456 testDocumentHasImages()1457 public void testDocumentHasImages() throws Exception, Throwable { 1458 if (!NullWebViewUtils.isWebViewAvailable()) { 1459 return; 1460 } 1461 final class DocumentHasImageCheckHandler extends Handler { 1462 private SettableFuture<Integer> mFuture; 1463 public DocumentHasImageCheckHandler(Looper looper) { 1464 super(looper); 1465 mFuture = SettableFuture.create(); 1466 } 1467 @Override 1468 public void handleMessage(Message msg) { 1469 mFuture.set(msg.arg1); 1470 } 1471 public Future<Integer> future() { 1472 return mFuture; 1473 } 1474 } 1475 1476 startWebServer(false); 1477 final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL); 1478 1479 // Create a handler on the UI thread. 1480 final DocumentHasImageCheckHandler handler = 1481 new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper()); 1482 1483 WebkitUtils.onMainThreadSync(() -> { 1484 mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\"" 1485 + imgUrl + "\"/></body></html>", "text/html", null); 1486 Message response = new Message(); 1487 response.setTarget(handler); 1488 assertFalse(handler.future().isDone()); 1489 mWebView.documentHasImages(response); 1490 }); 1491 assertEquals(1, (int)WebkitUtils.waitForFuture(handler.future())); 1492 } 1493 waitForFlingDone(WebViewOnUiThread webview)1494 private static void waitForFlingDone(WebViewOnUiThread webview) { 1495 class ScrollDiffPollingCheck extends PollingCheck { 1496 private static final long TIME_SLICE = 50; 1497 WebViewOnUiThread mWebView; 1498 private int mScrollX; 1499 private int mScrollY; 1500 1501 ScrollDiffPollingCheck(WebViewOnUiThread webview) { 1502 mWebView = webview; 1503 mScrollX = mWebView.getScrollX(); 1504 mScrollY = mWebView.getScrollY(); 1505 } 1506 1507 @Override 1508 protected boolean check() { 1509 try { 1510 Thread.sleep(TIME_SLICE); 1511 } catch (InterruptedException e) { 1512 // Intentionally ignored. 1513 } 1514 int newScrollX = mWebView.getScrollX(); 1515 int newScrollY = mWebView.getScrollY(); 1516 boolean flingDone = newScrollX == mScrollX && newScrollY == mScrollY; 1517 mScrollX = newScrollX; 1518 mScrollY = newScrollY; 1519 return flingDone; 1520 } 1521 } 1522 new ScrollDiffPollingCheck(webview).run(); 1523 } 1524 testPageScroll()1525 public void testPageScroll() throws Throwable { 1526 if (!NullWebViewUtils.isWebViewAvailable()) { 1527 return; 1528 } 1529 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1530 int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); 1531 String p = "<p style=\"height:" + dimension + "px;\">" + 1532 "Scroll by half the size of the page.</p>"; 1533 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1534 + p + "</body></html>", "text/html", null); 1535 1536 // Wait for UI thread to settle and receive page dimentions from renderer 1537 // such that we can invoke page down. 1538 new PollingCheck() { 1539 @Override 1540 protected boolean check() { 1541 return mOnUiThread.pageDown(false); 1542 } 1543 }.run(); 1544 1545 do { 1546 waitForFlingDone(mOnUiThread); 1547 } while (mOnUiThread.pageDown(false)); 1548 1549 waitForFlingDone(mOnUiThread); 1550 final int bottomScrollY = mOnUiThread.getScrollY(); 1551 1552 assertTrue(mOnUiThread.pageUp(false)); 1553 1554 do { 1555 waitForFlingDone(mOnUiThread); 1556 } while (mOnUiThread.pageUp(false)); 1557 1558 waitForFlingDone(mOnUiThread); 1559 final int topScrollY = mOnUiThread.getScrollY(); 1560 1561 // jump to the bottom 1562 assertTrue(mOnUiThread.pageDown(true)); 1563 new PollingCheck() { 1564 @Override 1565 protected boolean check() { 1566 return bottomScrollY == mOnUiThread.getScrollY(); 1567 } 1568 }.run(); 1569 1570 // jump to the top 1571 assertTrue(mOnUiThread.pageUp(true)); 1572 new PollingCheck() { 1573 @Override 1574 protected boolean check() { 1575 return topScrollY == mOnUiThread.getScrollY(); 1576 } 1577 }.run(); 1578 } 1579 testGetContentHeight()1580 public void testGetContentHeight() throws Throwable { 1581 if (!NullWebViewUtils.isWebViewAvailable()) { 1582 return; 1583 } 1584 mOnUiThread.loadDataAndWaitForCompletion( 1585 "<html><body></body></html>", "text/html", null); 1586 new PollingCheck() { 1587 @Override 1588 protected boolean check() { 1589 return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0 1590 && mOnUiThread.getHeight() != 0; 1591 } 1592 }.run(); 1593 1594 final int tolerance = 2; 1595 // getHeight() returns physical pixels and it is from web contents' size, getContentHeight() 1596 // returns CSS pixels and it is from compositor. In order to compare these two values, we 1597 // need to scale getContentHeight() by the device scale factor. This also amplifies any 1598 // rounding errors. Internally, getHeight() could also have rounding error first and then 1599 // times device scale factor, so we are comparing two rounded numbers below. 1600 // We allow 2 * getScale() as the delta, because getHeight() and getContentHeight() may 1601 // use different rounding algorithms and the results are from different computation 1602 // sequences. The extreme case is that in CSS pixel we have 2 as differences (0.9999 rounded 1603 // down and 1.0001 rounded up), therefore we ended with 2 * getScale(). 1604 assertEquals( 1605 mOnUiThread.getHeight(), 1606 mOnUiThread.getContentHeight() * mOnUiThread.getScale(), 1607 tolerance * Math.max(mOnUiThread.getScale(), 1.0f)); 1608 1609 // Make pageHeight bigger than the larger dimension of the device, so the page is taller 1610 // than viewport. Because when layout_height set to match_parent, getContentHeight() will 1611 // give maximum value between the actual web content height and the viewport height. When 1612 // viewport height is bigger, |extraSpace| below is not the extra space on the web page. 1613 // Note that we are passing physical pixels rather than CSS pixels here, when screen density 1614 // scale is lower than 1.0f, we need to scale it up. 1615 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1616 final float scaleFactor = Math.max(1.0f, 1.0f / mOnUiThread.getScale()); 1617 final int pageHeight = 1618 (int)(Math.ceil(Math.max(metrics.widthPixels, metrics.heightPixels) 1619 * scaleFactor)); 1620 1621 // set the margin to 0 1622 final String p = "<p style=\"height:" + pageHeight 1623 + "px;margin:0px auto;\">Get the height of HTML content.</p>"; 1624 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1625 + "</body></html>", "text/html", null); 1626 new PollingCheck() { 1627 @Override 1628 protected boolean check() { 1629 return mOnUiThread.getContentHeight() > pageHeight; 1630 } 1631 }.run(); 1632 1633 final int extraSpace = mOnUiThread.getContentHeight() - pageHeight; 1634 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1635 + p + "</body></html>", "text/html", null); 1636 new PollingCheck() { 1637 @Override 1638 protected boolean check() { 1639 // |pageHeight| is accurate, |extraSpace| = getContentheight() - |pageHeight|, so it 1640 // might have rounding error +-1, also getContentHeight() might have rounding error 1641 // +-1, so we allow error 2. Note that |pageHeight|, |extraSpace| and 1642 // getContentHeight() are all CSS pixels. 1643 final int expectedContentHeight = pageHeight + pageHeight + extraSpace; 1644 return Math.abs(expectedContentHeight - mOnUiThread.getContentHeight()) 1645 <= tolerance; 1646 } 1647 }.run(); 1648 } 1649 1650 @UiThreadTest testPlatformNotifications()1651 public void testPlatformNotifications() { 1652 if (!NullWebViewUtils.isWebViewAvailable()) { 1653 return; 1654 } 1655 WebView.enablePlatformNotifications(); 1656 WebView.disablePlatformNotifications(); 1657 } 1658 1659 @UiThreadTest testAccessPluginList()1660 public void testAccessPluginList() { 1661 if (!NullWebViewUtils.isWebViewAvailable()) { 1662 return; 1663 } 1664 assertNotNull(WebView.getPluginList()); 1665 1666 // can not find a way to install plugins 1667 mWebView.refreshPlugins(false); 1668 } 1669 1670 @UiThreadTest testDestroy()1671 public void testDestroy() { 1672 if (!NullWebViewUtils.isWebViewAvailable()) { 1673 return; 1674 } 1675 // Create a new WebView, since we cannot call destroy() on a view in the hierarchy 1676 WebView localWebView = new WebView(getActivity()); 1677 localWebView.destroy(); 1678 } 1679 testFlingScroll()1680 public void testFlingScroll() throws Throwable { 1681 if (!NullWebViewUtils.isWebViewAvailable()) { 1682 return; 1683 } 1684 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 1685 final int dimension = 10 * Math.max(metrics.widthPixels, metrics.heightPixels); 1686 String p = "<p style=\"height:" + dimension + "px;" + 1687 "width:" + dimension + "px\">Test fling scroll.</p>"; 1688 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1689 + "</body></html>", "text/html", null); 1690 new PollingCheck() { 1691 @Override 1692 protected boolean check() { 1693 return mOnUiThread.getContentHeight() >= dimension; 1694 } 1695 }.run(); 1696 getInstrumentation().waitForIdleSync(); 1697 1698 final int previousScrollX = mOnUiThread.getScrollX(); 1699 final int previousScrollY = mOnUiThread.getScrollY(); 1700 1701 mOnUiThread.flingScroll(10000, 10000); 1702 1703 new PollingCheck() { 1704 @Override 1705 protected boolean check() { 1706 return mOnUiThread.getScrollX() > previousScrollX && 1707 mOnUiThread.getScrollY() > previousScrollY; 1708 } 1709 }.run(); 1710 } 1711 testRequestFocusNodeHref()1712 public void testRequestFocusNodeHref() throws Throwable { 1713 if (!NullWebViewUtils.isWebViewAvailable()) { 1714 return; 1715 } 1716 startWebServer(false); 1717 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1718 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1719 final String links = "<DL><p><DT><A HREF=\"" + url1 1720 + "\">HTML_URL1</A><DT><A HREF=\"" + url2 1721 + "\">HTML_URL2</A></DL><p>"; 1722 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + links + "</body></html>", "text/html", null); 1723 getInstrumentation().waitForIdleSync(); 1724 1725 final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); 1726 final Message hrefMsg = new Message(); 1727 hrefMsg.setTarget(handler); 1728 1729 // focus on first link 1730 handler.reset(); 1731 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 1732 mOnUiThread.requestFocusNodeHref(hrefMsg); 1733 new PollingCheck() { 1734 @Override 1735 protected boolean check() { 1736 boolean done = false; 1737 if (handler.hasCalledHandleMessage()) { 1738 if (handler.mResultUrl != null) { 1739 done = true; 1740 } else { 1741 handler.reset(); 1742 Message newMsg = new Message(); 1743 newMsg.setTarget(handler); 1744 mOnUiThread.requestFocusNodeHref(newMsg); 1745 } 1746 } 1747 return done; 1748 } 1749 }.run(); 1750 assertEquals(url1, handler.getResultUrl()); 1751 1752 // focus on second link 1753 handler.reset(); 1754 final Message hrefMsg2 = new Message(); 1755 hrefMsg2.setTarget(handler); 1756 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 1757 mOnUiThread.requestFocusNodeHref(hrefMsg2); 1758 new PollingCheck() { 1759 @Override 1760 protected boolean check() { 1761 boolean done = false; 1762 final String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1763 if (handler.hasCalledHandleMessage()) { 1764 if (handler.mResultUrl != null && 1765 handler.mResultUrl.equals(url2)) { 1766 done = true; 1767 } else { 1768 handler.reset(); 1769 Message newMsg = new Message(); 1770 newMsg.setTarget(handler); 1771 mOnUiThread.requestFocusNodeHref(newMsg); 1772 } 1773 } 1774 return done; 1775 } 1776 }.run(); 1777 assertEquals(url2, handler.getResultUrl()); 1778 1779 mOnUiThread.requestFocusNodeHref(null); 1780 } 1781 testRequestImageRef()1782 public void testRequestImageRef() throws Exception, Throwable { 1783 if (!NullWebViewUtils.isWebViewAvailable()) { 1784 return; 1785 } 1786 final class ImageLoaded { 1787 public SettableFuture<Void> mImageLoaded = SettableFuture.create(); 1788 1789 @JavascriptInterface 1790 public void loaded() { 1791 mImageLoaded.set(null); 1792 } 1793 1794 public Future<Void> future() { 1795 return mImageLoaded; 1796 } 1797 } 1798 final ImageLoaded imageLoaded = new ImageLoaded(); 1799 mOnUiThread.getSettings().setJavaScriptEnabled(true); 1800 mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded"); 1801 AssetManager assets = getActivity().getAssets(); 1802 Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL)); 1803 int imgWidth = bitmap.getWidth(); 1804 int imgHeight = bitmap.getHeight(); 1805 1806 startWebServer(false); 1807 final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL); 1808 mOnUiThread.loadDataAndWaitForCompletion( 1809 "<html><head><title>Title</title><style type=\"text/css\">" 1810 + "%23imgElement { -webkit-transform: translate3d(0,0,1); }" 1811 + "%23imgElement.finish { -webkit-transform: translate3d(0,0,0);" 1812 + " -webkit-transition-duration: 1ms; }</style>" 1813 + "<script type=\"text/javascript\">function imgLoad() {" 1814 + "imgElement = document.getElementById('imgElement');" 1815 + "imgElement.addEventListener('webkitTransitionEnd'," 1816 + "function(e) { imageLoaded.loaded(); });" 1817 + "imgElement.className = 'finish';}</script>" 1818 + "</head><body><img id=\"imgElement\" src=\"" + imgUrl 1819 + "\" width=\"" + imgWidth + "\" height=\"" + imgHeight 1820 + "\" onLoad=\"imgLoad()\"/></body></html>", "text/html", null); 1821 WebkitUtils.waitForFuture(imageLoaded.future()); 1822 getInstrumentation().waitForIdleSync(); 1823 1824 final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper()); 1825 final Message msg = new Message(); 1826 msg.setTarget(handler); 1827 1828 // touch the image 1829 handler.reset(); 1830 int[] location = mOnUiThread.getLocationOnScreen(); 1831 1832 long time = SystemClock.uptimeMillis(); 1833 getInstrumentation().sendPointerSync( 1834 MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 1835 location[0] + imgWidth / 2, 1836 location[1] + imgHeight / 2, 0)); 1837 getInstrumentation().waitForIdleSync(); 1838 mOnUiThread.requestImageRef(msg); 1839 new PollingCheck() { 1840 @Override 1841 protected boolean check() { 1842 boolean done = false; 1843 if (handler.hasCalledHandleMessage()) { 1844 if (handler.mResultUrl != null) { 1845 done = true; 1846 } else { 1847 handler.reset(); 1848 Message newMsg = new Message(); 1849 newMsg.setTarget(handler); 1850 mOnUiThread.requestImageRef(newMsg); 1851 } 1852 } 1853 return done; 1854 } 1855 }.run(); 1856 assertEquals(imgUrl, handler.mResultUrl); 1857 } 1858 1859 @UiThreadTest testDebugDump()1860 public void testDebugDump() { 1861 if (!NullWebViewUtils.isWebViewAvailable()) { 1862 return; 1863 } 1864 mWebView.debugDump(); 1865 } 1866 testGetHitTestResult()1867 public void testGetHitTestResult() throws Throwable { 1868 if (!NullWebViewUtils.isWebViewAvailable()) { 1869 return; 1870 } 1871 final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1 1872 + "\">normal anchor</a></p>"; 1873 final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>"; 1874 final String form = "<p><form><input type=\"text\" name=\"Test\"><br>" 1875 + "<input type=\"submit\" value=\"Submit\"></form></p>"; 1876 String phoneNo = "3106984000"; 1877 final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>"; 1878 String email = "test@gmail.com"; 1879 final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>"; 1880 String location = "shanghai"; 1881 final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>"; 1882 1883 mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home", 1884 "<html><body>" + anchor + blankAnchor + form + tel + mailto + 1885 geo + "</body></html>", "text/html", "UTF-8", null); 1886 getInstrumentation().waitForIdleSync(); 1887 1888 // anchor 1889 moveFocusDown(); 1890 HitTestResult hitTestResult = mOnUiThread.getHitTestResult(); 1891 assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); 1892 assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra()); 1893 1894 // blank anchor 1895 moveFocusDown(); 1896 hitTestResult = mOnUiThread.getHitTestResult(); 1897 assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType()); 1898 assertEquals("fake://home", hitTestResult.getExtra()); 1899 1900 // text field 1901 moveFocusDown(); 1902 hitTestResult = mOnUiThread.getHitTestResult(); 1903 assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType()); 1904 assertNull(hitTestResult.getExtra()); 1905 1906 // submit button 1907 moveFocusDown(); 1908 hitTestResult = mOnUiThread.getHitTestResult(); 1909 assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType()); 1910 assertNull(hitTestResult.getExtra()); 1911 1912 // phone number 1913 moveFocusDown(); 1914 hitTestResult = mOnUiThread.getHitTestResult(); 1915 assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType()); 1916 assertEquals(phoneNo, hitTestResult.getExtra()); 1917 1918 // email 1919 moveFocusDown(); 1920 hitTestResult = mOnUiThread.getHitTestResult(); 1921 assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType()); 1922 assertEquals(email, hitTestResult.getExtra()); 1923 1924 // geo address 1925 moveFocusDown(); 1926 hitTestResult = mOnUiThread.getHitTestResult(); 1927 assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType()); 1928 assertEquals(location, hitTestResult.getExtra()); 1929 } 1930 testSetInitialScale()1931 public void testSetInitialScale() throws Throwable { 1932 if (!NullWebViewUtils.isWebViewAvailable()) { 1933 return; 1934 } 1935 final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>"; 1936 final float defaultScale = 1937 getActivity().getResources().getDisplayMetrics().density; 1938 1939 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1940 + "</body></html>", "text/html", null); 1941 1942 new PollingCheck(TEST_TIMEOUT) { 1943 @Override 1944 protected boolean check() { 1945 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1946 } 1947 }.run(); 1948 1949 mOnUiThread.setInitialScale(0); 1950 // modify content to fool WebKit into re-loading 1951 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1952 + "2" + "</body></html>", "text/html", null); 1953 1954 new PollingCheck(TEST_TIMEOUT) { 1955 @Override 1956 protected boolean check() { 1957 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1958 } 1959 }.run(); 1960 1961 mOnUiThread.setInitialScale(50); 1962 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1963 + "3" + "</body></html>", "text/html", null); 1964 1965 new PollingCheck(TEST_TIMEOUT) { 1966 @Override 1967 protected boolean check() { 1968 return Math.abs(0.5 - mOnUiThread.getScale()) < .01f; 1969 } 1970 }.run(); 1971 1972 mOnUiThread.setInitialScale(0); 1973 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 1974 + "4" + "</body></html>", "text/html", null); 1975 1976 new PollingCheck(TEST_TIMEOUT) { 1977 @Override 1978 protected boolean check() { 1979 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f; 1980 } 1981 }.run(); 1982 } 1983 1984 @UiThreadTest testClearHistory()1985 public void testClearHistory() throws Exception { 1986 if (!NullWebViewUtils.isWebViewAvailable()) { 1987 return; 1988 } 1989 startWebServer(false); 1990 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 1991 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 1992 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 1993 1994 mOnUiThread.loadUrlAndWaitForCompletion(url1); 1995 pollingCheckWebBackForwardList(url1, 0, 1); 1996 1997 mOnUiThread.loadUrlAndWaitForCompletion(url2); 1998 pollingCheckWebBackForwardList(url2, 1, 2); 1999 2000 mOnUiThread.loadUrlAndWaitForCompletion(url3); 2001 pollingCheckWebBackForwardList(url3, 2, 3); 2002 2003 mWebView.clearHistory(); 2004 2005 // only current URL is left after clearing 2006 pollingCheckWebBackForwardList(url3, 0, 1); 2007 } 2008 2009 @UiThreadTest testSaveAndRestoreState()2010 public void testSaveAndRestoreState() throws Throwable { 2011 if (!NullWebViewUtils.isWebViewAvailable()) { 2012 return; 2013 } 2014 assertNull("Should return null when there's nothing to save", 2015 mWebView.saveState(new Bundle())); 2016 2017 startWebServer(false); 2018 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1); 2019 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2); 2020 String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3); 2021 2022 // make a history list 2023 mOnUiThread.loadUrlAndWaitForCompletion(url1); 2024 pollingCheckWebBackForwardList(url1, 0, 1); 2025 mOnUiThread.loadUrlAndWaitForCompletion(url2); 2026 pollingCheckWebBackForwardList(url2, 1, 2); 2027 mOnUiThread.loadUrlAndWaitForCompletion(url3); 2028 pollingCheckWebBackForwardList(url3, 2, 3); 2029 2030 // save the list 2031 Bundle bundle = new Bundle(); 2032 WebBackForwardList saveList = mWebView.saveState(bundle); 2033 assertNotNull(saveList); 2034 assertEquals(3, saveList.getSize()); 2035 assertEquals(2, saveList.getCurrentIndex()); 2036 assertEquals(url1, saveList.getItemAtIndex(0).getUrl()); 2037 assertEquals(url2, saveList.getItemAtIndex(1).getUrl()); 2038 assertEquals(url3, saveList.getItemAtIndex(2).getUrl()); 2039 2040 // change the content to a new "blank" web view without history 2041 final WebView newWebView = new WebView(getActivity()); 2042 2043 WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList(); 2044 assertNotNull(copyListBeforeRestore); 2045 assertEquals(0, copyListBeforeRestore.getSize()); 2046 2047 // restore the list 2048 final WebBackForwardList restoreList = newWebView.restoreState(bundle); 2049 assertNotNull(restoreList); 2050 assertEquals(3, restoreList.getSize()); 2051 assertEquals(2, saveList.getCurrentIndex()); 2052 2053 // wait for the list items to get inflated 2054 new PollingCheck(TEST_TIMEOUT) { 2055 @Override 2056 protected boolean check() { 2057 return restoreList.getItemAtIndex(0).getUrl() != null && 2058 restoreList.getItemAtIndex(1).getUrl() != null && 2059 restoreList.getItemAtIndex(2).getUrl() != null; 2060 } 2061 }.run(); 2062 assertEquals(url1, restoreList.getItemAtIndex(0).getUrl()); 2063 assertEquals(url2, restoreList.getItemAtIndex(1).getUrl()); 2064 assertEquals(url3, restoreList.getItemAtIndex(2).getUrl()); 2065 2066 WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList(); 2067 assertNotNull(copyListAfterRestore); 2068 assertEquals(3, copyListAfterRestore.getSize()); 2069 assertEquals(2, copyListAfterRestore.getCurrentIndex()); 2070 assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl()); 2071 assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl()); 2072 assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl()); 2073 } 2074 testRequestChildRectangleOnScreen()2075 public void testRequestChildRectangleOnScreen() throws Throwable { 2076 if (!NullWebViewUtils.isWebViewAvailable()) { 2077 return; 2078 } 2079 2080 // It is needed to make test pass on some devices. 2081 mOnUiThread.setLayoutToMatchParent(); 2082 2083 DisplayMetrics metrics = mOnUiThread.getDisplayMetrics(); 2084 final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels); 2085 String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\"> </p>"; 2086 mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p 2087 + "</body></html>", "text/html", null); 2088 new PollingCheck() { 2089 @Override 2090 protected boolean check() { 2091 return mOnUiThread.getContentHeight() >= dimension; 2092 } 2093 }.run(); 2094 2095 int origX = mOnUiThread.getScrollX(); 2096 int origY = mOnUiThread.getScrollY(); 2097 2098 int half = dimension / 2; 2099 Rect rect = new Rect(half, half, half + 1, half + 1); 2100 assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true)); 2101 assertThat(mOnUiThread.getScrollX(), greaterThan(origX)); 2102 assertThat(mOnUiThread.getScrollY(), greaterThan(origY)); 2103 } 2104 testSetDownloadListener()2105 public void testSetDownloadListener() throws Throwable { 2106 if (!NullWebViewUtils.isWebViewAvailable()) { 2107 return; 2108 } 2109 2110 final SettableFuture<Void> downloadStartFuture = SettableFuture.create(); 2111 final class MyDownloadListener implements DownloadListener { 2112 public String url; 2113 public String mimeType; 2114 public long contentLength; 2115 public String contentDisposition; 2116 2117 @Override 2118 public void onDownloadStart(String url, String userAgent, String contentDisposition, 2119 String mimetype, long contentLength) { 2120 this.url = url; 2121 this.mimeType = mimetype; 2122 this.contentLength = contentLength; 2123 this.contentDisposition = contentDisposition; 2124 downloadStartFuture.set(null); 2125 } 2126 } 2127 2128 final String mimeType = "application/octet-stream"; 2129 final int length = 100; 2130 final MyDownloadListener listener = new MyDownloadListener(); 2131 2132 startWebServer(false); 2133 final String url = mWebServer.getBinaryUrl(mimeType, length); 2134 2135 // By default, WebView sends an intent to ask the system to 2136 // handle loading a new URL. We set WebViewClient as 2137 // WebViewClient.shouldOverrideUrlLoading() returns false, so 2138 // the WebView will load the new URL. 2139 mOnUiThread.setDownloadListener(listener); 2140 mOnUiThread.getSettings().setJavaScriptEnabled(true); 2141 mOnUiThread.loadDataAndWaitForCompletion( 2142 "<html><body onload=\"window.location = \'" + url + "\'\"></body></html>", 2143 "text/html", null); 2144 // Wait for layout to complete before setting focus. 2145 getInstrumentation().waitForIdleSync(); 2146 2147 WebkitUtils.waitForFuture(downloadStartFuture); 2148 assertEquals(url, listener.url); 2149 assertTrue(listener.contentDisposition.contains("test.bin")); 2150 assertEquals(length, listener.contentLength); 2151 assertEquals(mimeType, listener.mimeType); 2152 } 2153 2154 @UiThreadTest testSetLayoutParams()2155 public void testSetLayoutParams() { 2156 if (!NullWebViewUtils.isWebViewAvailable()) { 2157 return; 2158 } 2159 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800); 2160 mWebView.setLayoutParams(params); 2161 assertSame(params, mWebView.getLayoutParams()); 2162 } 2163 2164 @UiThreadTest testSetMapTrackballToArrowKeys()2165 public void testSetMapTrackballToArrowKeys() { 2166 if (!NullWebViewUtils.isWebViewAvailable()) { 2167 return; 2168 } 2169 mWebView.setMapTrackballToArrowKeys(true); 2170 } 2171 testSetNetworkAvailable()2172 public void testSetNetworkAvailable() throws Exception { 2173 if (!NullWebViewUtils.isWebViewAvailable()) { 2174 return; 2175 } 2176 WebSettings settings = mOnUiThread.getSettings(); 2177 settings.setJavaScriptEnabled(true); 2178 startWebServer(false); 2179 2180 String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL); 2181 mOnUiThread.loadUrlAndWaitForCompletion(url); 2182 assertEquals("ONLINE", mOnUiThread.getTitle()); 2183 2184 mOnUiThread.setNetworkAvailable(false); 2185 2186 // Wait for the DOM to receive notification of the network state change. 2187 new PollingCheck(TEST_TIMEOUT) { 2188 @Override 2189 protected boolean check() { 2190 return mOnUiThread.getTitle().equals("OFFLINE"); 2191 } 2192 }.run(); 2193 2194 mOnUiThread.setNetworkAvailable(true); 2195 2196 // Wait for the DOM to receive notification of the network state change. 2197 new PollingCheck(TEST_TIMEOUT) { 2198 @Override 2199 protected boolean check() { 2200 return mOnUiThread.getTitle().equals("ONLINE"); 2201 } 2202 }.run(); 2203 } 2204 testSetWebChromeClient()2205 public void testSetWebChromeClient() throws Throwable { 2206 if (!NullWebViewUtils.isWebViewAvailable()) { 2207 return; 2208 } 2209 2210 final SettableFuture<Void> future = SettableFuture.create(); 2211 mOnUiThread.setWebChromeClient(new WaitForProgressClient(mOnUiThread) { 2212 @Override 2213 public void onProgressChanged(WebView view, int newProgress) { 2214 super.onProgressChanged(view, newProgress); 2215 future.set(null); 2216 } 2217 }); 2218 getInstrumentation().waitForIdleSync(); 2219 assertFalse(future.isDone()); 2220 2221 startWebServer(false); 2222 final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 2223 mOnUiThread.loadUrlAndWaitForCompletion(url); 2224 getInstrumentation().waitForIdleSync(); 2225 2226 WebkitUtils.waitForFuture(future); 2227 } 2228 testPauseResumeTimers()2229 public void testPauseResumeTimers() throws Throwable { 2230 if (!NullWebViewUtils.isWebViewAvailable()) { 2231 return; 2232 } 2233 class Monitor { 2234 private boolean mIsUpdated; 2235 2236 @JavascriptInterface 2237 public synchronized void update() { 2238 mIsUpdated = true; 2239 notify(); 2240 } 2241 public synchronized boolean waitForUpdate() { 2242 while (!mIsUpdated) { 2243 try { 2244 // This is slightly flaky, as we can't guarantee that 2245 // this is a sufficient time limit, but there's no way 2246 // around this. 2247 wait(1000); 2248 if (!mIsUpdated) { 2249 return false; 2250 } 2251 } catch (InterruptedException e) { 2252 } 2253 } 2254 mIsUpdated = false; 2255 return true; 2256 } 2257 }; 2258 final Monitor monitor = new Monitor(); 2259 final String updateMonitorHtml = "<html>" + 2260 "<body onload=\"monitor.update();\"></body></html>"; 2261 2262 // Test that JavaScript is executed even with timers paused. 2263 WebkitUtils.onMainThreadSync(() -> { 2264 mWebView.getSettings().setJavaScriptEnabled(true); 2265 mWebView.addJavascriptInterface(monitor, "monitor"); 2266 mWebView.pauseTimers(); 2267 mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml, 2268 "text/html", null); 2269 }); 2270 assertTrue(monitor.waitForUpdate()); 2271 2272 // Start a timer and test that it does not fire. 2273 mOnUiThread.loadDataAndWaitForCompletion( 2274 "<html><body onload='setTimeout(function(){monitor.update();},100)'>" + 2275 "</body></html>", "text/html", null); 2276 assertFalse(monitor.waitForUpdate()); 2277 2278 // Resume timers and test that the timer fires. 2279 mOnUiThread.resumeTimers(); 2280 assertTrue(monitor.waitForUpdate()); 2281 } 2282 2283 // verify query parameters can be passed correctly to android asset files testAndroidAssetQueryParam()2284 public void testAndroidAssetQueryParam() { 2285 if (!NullWebViewUtils.isWebViewAvailable()) { 2286 return; 2287 } 2288 2289 WebSettings settings = mOnUiThread.getSettings(); 2290 settings.setJavaScriptEnabled(true); 2291 // test passing a parameter 2292 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL+"?val=SUCCESS"); 2293 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 2294 assertEquals("SUCCESS", mOnUiThread.getTitle()); 2295 } 2296 2297 // verify anchors work correctly for android asset files testAndroidAssetAnchor()2298 public void testAndroidAssetAnchor() { 2299 if (!NullWebViewUtils.isWebViewAvailable()) { 2300 return; 2301 } 2302 2303 WebSettings settings = mOnUiThread.getSettings(); 2304 settings.setJavaScriptEnabled(true); 2305 // test using an anchor 2306 String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL+"#anchor"); 2307 mOnUiThread.loadUrlAndWaitForCompletion(fileUrl); 2308 assertEquals("anchor", mOnUiThread.getTitle()); 2309 } 2310 testEvaluateJavascript()2311 public void testEvaluateJavascript() { 2312 if (!NullWebViewUtils.isWebViewAvailable()) { 2313 return; 2314 } 2315 mOnUiThread.getSettings().setJavaScriptEnabled(true); 2316 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 2317 2318 assertEquals("2", mOnUiThread.evaluateJavascriptSync("1+1")); 2319 2320 assertEquals("9", mOnUiThread.evaluateJavascriptSync("1+1; 4+5")); 2321 2322 final String EXPECTED_TITLE = "test"; 2323 mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null); 2324 new PollingCheck(TEST_TIMEOUT) { 2325 @Override 2326 protected boolean check() { 2327 return mOnUiThread.getTitle().equals(EXPECTED_TITLE); 2328 } 2329 }.run(); 2330 } 2331 2332 // Verify Print feature can create a PDF file with a correct preamble. testPrinting()2333 public void testPrinting() throws Throwable { 2334 if (!NullWebViewUtils.isWebViewAvailable()) { 2335 return; 2336 } 2337 mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" + 2338 "<body>foo</body></html>", 2339 "text/html", null); 2340 final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter(); 2341 printDocumentStart(adapter); 2342 PrintAttributes attributes = new PrintAttributes.Builder() 2343 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 2344 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) 2345 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) 2346 .build(); 2347 final WebViewCtsActivity activity = getActivity(); 2348 final File file = activity.getFileStreamPath(PRINTER_TEST_FILE); 2349 final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, 2350 ParcelFileDescriptor.parseMode("w")); 2351 final SettableFuture<Void> result = SettableFuture.create(); 2352 printDocumentLayout(adapter, null, attributes, 2353 new LayoutResultCallback() { 2354 // Called on UI thread 2355 @Override 2356 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 2357 PageRange[] pageRanges = new PageRange[] {PageRange.ALL_PAGES}; 2358 savePrintedPage(adapter, descriptor, pageRanges, result); 2359 } 2360 }); 2361 try { 2362 WebkitUtils.waitForFuture(result); 2363 assertThat(file.length(), greaterThan(0L)); 2364 FileInputStream in = new FileInputStream(file); 2365 byte[] b = new byte[PDF_PREAMBLE.length()]; 2366 in.read(b); 2367 String preamble = new String(b); 2368 assertEquals(PDF_PREAMBLE, preamble); 2369 } finally { 2370 // close the descriptor, if not closed already. 2371 descriptor.close(); 2372 file.delete(); 2373 } 2374 } 2375 2376 // Verify Print feature can create a PDF file with correct number of pages. testPrintingPagesCount()2377 public void testPrintingPagesCount() throws Throwable { 2378 if (!NullWebViewUtils.isWebViewAvailable()) { 2379 return; 2380 } 2381 String content = "<html><head></head><body>"; 2382 for (int i = 0; i < 500; ++i) { 2383 content += "<br />abcdefghijk<br />"; 2384 } 2385 content += "</body></html>"; 2386 mOnUiThread.loadDataAndWaitForCompletion(content, "text/html", null); 2387 final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter(); 2388 printDocumentStart(adapter); 2389 PrintAttributes attributes = new PrintAttributes.Builder() 2390 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 2391 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) 2392 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) 2393 .build(); 2394 final WebViewCtsActivity activity = getActivity(); 2395 final File file = activity.getFileStreamPath(PRINTER_TEST_FILE); 2396 final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file, 2397 ParcelFileDescriptor.parseMode("w")); 2398 final SettableFuture<Void> result = SettableFuture.create(); 2399 printDocumentLayout(adapter, null, attributes, 2400 new LayoutResultCallback() { 2401 // Called on UI thread 2402 @Override 2403 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 2404 PageRange[] pageRanges = new PageRange[] { 2405 new PageRange(1, 1), new PageRange(4, 7) 2406 }; 2407 savePrintedPage(adapter, descriptor, pageRanges, result); 2408 } 2409 }); 2410 try { 2411 WebkitUtils.waitForFuture(result); 2412 assertThat(file.length(), greaterThan(0L)); 2413 PdfRenderer renderer = new PdfRenderer( 2414 ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)); 2415 assertEquals(5, renderer.getPageCount()); 2416 } finally { 2417 descriptor.close(); 2418 file.delete(); 2419 } 2420 } 2421 2422 /** 2423 * This should remain functionally equivalent to 2424 * androidx.webkit.WebViewCompatTest#testVisualStateCallbackCalled. Modifications to this test 2425 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2426 */ testVisualStateCallbackCalled()2427 public void testVisualStateCallbackCalled() throws Exception { 2428 // Check that the visual state callback is called correctly. 2429 if (!NullWebViewUtils.isWebViewAvailable()) { 2430 return; 2431 } 2432 2433 final long kRequest = 100; 2434 2435 mOnUiThread.loadUrl("about:blank"); 2436 2437 final SettableFuture<Long> visualStateFuture = SettableFuture.create(); 2438 mOnUiThread.postVisualStateCallback(kRequest, new VisualStateCallback() { 2439 public void onComplete(long requestId) { 2440 visualStateFuture.set(requestId); 2441 } 2442 }); 2443 2444 assertEquals(kRequest, (long) WebkitUtils.waitForFuture(visualStateFuture)); 2445 } 2446 2447 /** 2448 * This should remain functionally equivalent to 2449 * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithMalformedList. 2450 * Modifications to this test should be reflected in that test as necessary. See 2451 * http://go/modifying-webview-cts. 2452 */ testSetSafeBrowsingAllowlistWithMalformedList()2453 public void testSetSafeBrowsingAllowlistWithMalformedList() throws Exception { 2454 if (!NullWebViewUtils.isWebViewAvailable()) { 2455 return; 2456 } 2457 2458 List allowlist = new ArrayList<String>(); 2459 // Protocols are not supported in the allowlist 2460 allowlist.add("http://google.com"); 2461 final SettableFuture<Boolean> safeBrowsingAllowlistFuture = SettableFuture.create(); 2462 WebView.setSafeBrowsingWhitelist(allowlist, new ValueCallback<Boolean>() { 2463 @Override 2464 public void onReceiveValue(Boolean success) { 2465 safeBrowsingAllowlistFuture.set(success); 2466 } 2467 }); 2468 assertFalse(WebkitUtils.waitForFuture(safeBrowsingAllowlistFuture)); 2469 } 2470 2471 /** 2472 * This should remain functionally equivalent to 2473 * androidx.webkit.WebViewCompatTest#testSetSafeBrowsingAllowlistWithValidList. Modifications 2474 * to this test should be reflected in that test as necessary. See 2475 * http://go/modifying-webview-cts. 2476 */ testSetSafeBrowsingAllowlistWithValidList()2477 public void testSetSafeBrowsingAllowlistWithValidList() throws Exception { 2478 if (!NullWebViewUtils.isWebViewAvailable()) { 2479 return; 2480 } 2481 2482 List allowlist = new ArrayList<String>(); 2483 allowlist.add("safe-browsing"); 2484 final SettableFuture<Boolean> safeBrowsingAllowlistFuture = SettableFuture.create(); 2485 WebView.setSafeBrowsingWhitelist(allowlist, new ValueCallback<Boolean>() { 2486 @Override 2487 public void onReceiveValue(Boolean success) { 2488 safeBrowsingAllowlistFuture.set(success); 2489 } 2490 }); 2491 assertTrue(WebkitUtils.waitForFuture(safeBrowsingAllowlistFuture)); 2492 2493 final SettableFuture<Void> pageFinishedFuture = SettableFuture.create(); 2494 mOnUiThread.setWebViewClient(new WebViewClient() { 2495 @Override 2496 public void onPageFinished(WebView view, String url) { 2497 pageFinishedFuture.set(null); 2498 } 2499 2500 @Override 2501 public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType, 2502 SafeBrowsingResponse callback) { 2503 pageFinishedFuture.setException(new IllegalStateException( 2504 "Should not invoke onSafeBrowsingHit")); 2505 } 2506 }); 2507 2508 mOnUiThread.loadUrl("chrome://safe-browsing/match?type=malware"); 2509 2510 // Wait until page load has completed 2511 WebkitUtils.waitForFuture(pageFinishedFuture); 2512 } 2513 2514 /** 2515 * This should remain functionally equivalent to 2516 * androidx.webkit.WebViewCompatTest#testGetWebViewClient. Modifications to this test should be 2517 * reflected in that test as necessary. See http://go/modifying-webview-cts. 2518 */ 2519 @UiThreadTest testGetWebViewClient()2520 public void testGetWebViewClient() throws Exception { 2521 if (!NullWebViewUtils.isWebViewAvailable()) { 2522 return; 2523 } 2524 2525 // getWebViewClient should return a default WebViewClient if it hasn't been set yet 2526 WebView webView = new WebView(getActivity()); 2527 WebViewClient client = webView.getWebViewClient(); 2528 assertNotNull(client); 2529 assertTrue(client instanceof WebViewClient); 2530 2531 // getWebViewClient should return the client after it has been set 2532 WebViewClient client2 = new WebViewClient(); 2533 assertNotSame(client, client2); 2534 webView.setWebViewClient(client2); 2535 assertSame(client2, webView.getWebViewClient()); 2536 } 2537 2538 /** 2539 * This should remain functionally equivalent to 2540 * androidx.webkit.WebViewCompatTest#testGetWebChromeClient. Modifications to this test should 2541 * be reflected in that test as necessary. See http://go/modifying-webview-cts. 2542 */ 2543 @UiThreadTest testGetWebChromeClient()2544 public void testGetWebChromeClient() throws Exception { 2545 if (!NullWebViewUtils.isWebViewAvailable()) { 2546 return; 2547 } 2548 2549 // getWebChromeClient should return null if the client hasn't been set yet 2550 WebView webView = new WebView(getActivity()); 2551 WebChromeClient client = webView.getWebChromeClient(); 2552 assertNull(client); 2553 2554 // getWebChromeClient should return the client after it has been set 2555 WebChromeClient client2 = new WebChromeClient(); 2556 assertNotSame(client, client2); 2557 webView.setWebChromeClient(client2); 2558 assertSame(client2, webView.getWebChromeClient()); 2559 } 2560 2561 @UiThreadTest testSetCustomTextClassifier()2562 public void testSetCustomTextClassifier() throws Exception { 2563 if (!NullWebViewUtils.isWebViewAvailable()) { 2564 return; 2565 } 2566 2567 class CustomTextClassifier implements TextClassifier { 2568 @Override 2569 public TextSelection suggestSelection( 2570 CharSequence text, 2571 int startIndex, 2572 int endIndex, 2573 LocaleList defaultLocales) { 2574 return new TextSelection.Builder(0, 1).build(); 2575 } 2576 2577 @Override 2578 public TextClassification classifyText( 2579 CharSequence text, 2580 int startIndex, 2581 int endIndex, 2582 LocaleList defaultLocales) { 2583 return new TextClassification.Builder().build(); 2584 } 2585 }; 2586 2587 TextClassifier classifier = new CustomTextClassifier(); 2588 WebView webView = new WebView(getActivity()); 2589 webView.setTextClassifier(classifier); 2590 assertSame(webView.getTextClassifier(), classifier); 2591 } 2592 2593 private static class MockContext extends ContextWrapper { 2594 private boolean mGetApplicationContextWasCalled; 2595 MockContext(Context context)2596 public MockContext(Context context) { 2597 super(context); 2598 } 2599 getApplicationContext()2600 public Context getApplicationContext() { 2601 mGetApplicationContextWasCalled = true; 2602 return super.getApplicationContext(); 2603 } 2604 wasGetApplicationContextCalled()2605 public boolean wasGetApplicationContextCalled() { 2606 return mGetApplicationContextWasCalled; 2607 } 2608 } 2609 2610 /** 2611 * This should remain functionally equivalent to 2612 * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingUseApplicationContext. Modifications to 2613 * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2614 */ testStartSafeBrowsingUseApplicationContext()2615 public void testStartSafeBrowsingUseApplicationContext() throws Exception { 2616 if (!NullWebViewUtils.isWebViewAvailable()) { 2617 return; 2618 } 2619 2620 final MockContext ctx = new MockContext(getActivity()); 2621 final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create(); 2622 WebView.startSafeBrowsing(ctx, new ValueCallback<Boolean>() { 2623 @Override 2624 public void onReceiveValue(Boolean value) { 2625 startSafeBrowsingFuture.set(ctx.wasGetApplicationContextCalled()); 2626 return; 2627 } 2628 }); 2629 assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture)); 2630 } 2631 2632 /** 2633 * This should remain functionally equivalent to 2634 * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingWithNullCallbackDoesntCrash. 2635 * Modifications to this test should be reflected in that test as necessary. See 2636 * http://go/modifying-webview-cts. 2637 */ testStartSafeBrowsingWithNullCallbackDoesntCrash()2638 public void testStartSafeBrowsingWithNullCallbackDoesntCrash() throws Exception { 2639 if (!NullWebViewUtils.isWebViewAvailable()) { 2640 return; 2641 } 2642 2643 WebView.startSafeBrowsing(getActivity().getApplicationContext(), null); 2644 } 2645 2646 /** 2647 * This should remain functionally equivalent to 2648 * androidx.webkit.WebViewCompatTest#testStartSafeBrowsingInvokesCallback. Modifications to 2649 * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2650 */ testStartSafeBrowsingInvokesCallback()2651 public void testStartSafeBrowsingInvokesCallback() throws Exception { 2652 if (!NullWebViewUtils.isWebViewAvailable()) { 2653 return; 2654 } 2655 2656 final SettableFuture<Boolean> startSafeBrowsingFuture = SettableFuture.create(); 2657 WebView.startSafeBrowsing(getActivity().getApplicationContext(), 2658 new ValueCallback<Boolean>() { 2659 @Override 2660 public void onReceiveValue(Boolean value) { 2661 startSafeBrowsingFuture.set(Looper.getMainLooper().isCurrentThread()); 2662 return; 2663 } 2664 }); 2665 assertTrue(WebkitUtils.waitForFuture(startSafeBrowsingFuture)); 2666 } 2667 savePrintedPage(final PrintDocumentAdapter adapter, final ParcelFileDescriptor descriptor, final PageRange[] pageRanges, final SettableFuture<Void> result)2668 private void savePrintedPage(final PrintDocumentAdapter adapter, 2669 final ParcelFileDescriptor descriptor, final PageRange[] pageRanges, 2670 final SettableFuture<Void> result) { 2671 adapter.onWrite(pageRanges, descriptor, 2672 new CancellationSignal(), 2673 new WriteResultCallback() { 2674 @Override 2675 public void onWriteFinished(PageRange[] pages) { 2676 try { 2677 descriptor.close(); 2678 result.set(null); 2679 } catch (IOException ex) { 2680 result.setException(ex); 2681 } 2682 } 2683 }); 2684 } 2685 printDocumentStart(final PrintDocumentAdapter adapter)2686 private void printDocumentStart(final PrintDocumentAdapter adapter) { 2687 WebkitUtils.onMainThreadSync(() -> { 2688 adapter.onStart(); 2689 }); 2690 } 2691 printDocumentLayout(final PrintDocumentAdapter adapter, final PrintAttributes oldAttributes, final PrintAttributes newAttributes, final LayoutResultCallback layoutResultCallback)2692 private void printDocumentLayout(final PrintDocumentAdapter adapter, 2693 final PrintAttributes oldAttributes, final PrintAttributes newAttributes, 2694 final LayoutResultCallback layoutResultCallback) { 2695 WebkitUtils.onMainThreadSync(() -> { 2696 adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(), 2697 layoutResultCallback, null); 2698 }); 2699 } 2700 2701 private static class HrefCheckHandler extends Handler { 2702 private boolean mHadRecieved; 2703 2704 private String mResultUrl; 2705 HrefCheckHandler(Looper looper)2706 public HrefCheckHandler(Looper looper) { 2707 super(looper); 2708 } 2709 hasCalledHandleMessage()2710 public boolean hasCalledHandleMessage() { 2711 return mHadRecieved; 2712 } 2713 getResultUrl()2714 public String getResultUrl() { 2715 return mResultUrl; 2716 } 2717 reset()2718 public void reset(){ 2719 mResultUrl = null; 2720 mHadRecieved = false; 2721 } 2722 2723 @Override handleMessage(Message msg)2724 public void handleMessage(Message msg) { 2725 mResultUrl = msg.getData().getString("url"); 2726 mHadRecieved = true; 2727 } 2728 } 2729 moveFocusDown()2730 private void moveFocusDown() throws Throwable { 2731 // send down key and wait for idle 2732 getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB); 2733 // waiting for idle isn't always sufficient for the key to be fully processed 2734 Thread.sleep(500); 2735 } 2736 pollingCheckWebBackForwardList(final String currUrl, final int currIndex, final int size)2737 private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex, 2738 final int size) { 2739 new PollingCheck() { 2740 @Override 2741 protected boolean check() { 2742 WebBackForwardList list = mWebView.copyBackForwardList(); 2743 return checkWebBackForwardList(list, currUrl, currIndex, size); 2744 } 2745 }.run(); 2746 } 2747 checkWebBackForwardList(WebBackForwardList list, String currUrl, int currIndex, int size)2748 private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl, 2749 int currIndex, int size) { 2750 return (list != null) 2751 && (list.getSize() == size) 2752 && (list.getCurrentIndex() == currIndex) 2753 && list.getItemAtIndex(currIndex).getUrl().equals(currUrl); 2754 } 2755 assertGoBackOrForwardBySteps(boolean expected, int steps)2756 private void assertGoBackOrForwardBySteps(boolean expected, int steps) { 2757 // skip if steps equals to 0 2758 if (steps == 0) 2759 return; 2760 2761 int start = steps > 0 ? 1 : steps; 2762 int end = steps > 0 ? steps : -1; 2763 2764 // check all the steps in the history 2765 for (int i = start; i <= end; i++) { 2766 assertEquals(expected, mWebView.canGoBackOrForward(i)); 2767 2768 // shortcut methods for one step 2769 if (i == 1) { 2770 assertEquals(expected, mWebView.canGoForward()); 2771 } else if (i == -1) { 2772 assertEquals(expected, mWebView.canGoBack()); 2773 } 2774 } 2775 } 2776 isPictureFilledWithColor(Picture picture, int color)2777 private boolean isPictureFilledWithColor(Picture picture, int color) { 2778 if (picture.getWidth() == 0 || picture.getHeight() == 0) 2779 return false; 2780 2781 Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), 2782 Config.ARGB_8888); 2783 picture.draw(new Canvas(bitmap)); 2784 2785 for (int i = 0; i < bitmap.getWidth(); i ++) { 2786 for (int j = 0; j < bitmap.getHeight(); j ++) { 2787 if (color != bitmap.getPixel(i, j)) { 2788 return false; 2789 } 2790 } 2791 } 2792 return true; 2793 } 2794 2795 /** 2796 * Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started, 2797 * scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once 2798 * changes have stopped, the function exits. If no scrolling has happened 2799 * then the function exits after MIN_SCROLL_WAIT milliseconds. 2800 * @param previousScrollY The Y scroll position prior to waiting. 2801 */ waitForScrollingComplete(int previousScrollY)2802 private void waitForScrollingComplete(int previousScrollY) 2803 throws InterruptedException { 2804 int scrollY = previousScrollY; 2805 // wait at least MIN_SCROLL_WAIT for something to happen. 2806 long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS; 2807 boolean scrollChanging = false; 2808 boolean scrollChanged = false; 2809 boolean minWaitExpired = false; 2810 while (scrollChanging || (!scrollChanged && !minWaitExpired)) { 2811 Thread.sleep(SCROLL_WAIT_INTERVAL_MS); 2812 int oldScrollY = scrollY; 2813 scrollY = mOnUiThread.getScrollY(); 2814 scrollChanging = (scrollY != oldScrollY); 2815 scrollChanged = (scrollY != previousScrollY); 2816 minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait); 2817 } 2818 } 2819 2820 /** 2821 * This should remain functionally equivalent to 2822 * androidx.webkit.WebViewCompatTest#testGetSafeBrowsingPrivacyPolicyUrl. Modifications to this 2823 * test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 2824 */ testGetSafeBrowsingPrivacyPolicyUrl()2825 public void testGetSafeBrowsingPrivacyPolicyUrl() throws Exception { 2826 if (!NullWebViewUtils.isWebViewAvailable()) { 2827 return; 2828 } 2829 2830 assertNotNull(WebView.getSafeBrowsingPrivacyPolicyUrl()); 2831 try { 2832 new URL(WebView.getSafeBrowsingPrivacyPolicyUrl().toString()); 2833 } catch (MalformedURLException e) { 2834 fail("The privacy policy URL should be a well-formed URL"); 2835 } 2836 } 2837 testWebViewClassLoaderReturnsNonNull()2838 public void testWebViewClassLoaderReturnsNonNull() { 2839 if (!NullWebViewUtils.isWebViewAvailable()) { 2840 return; 2841 } 2842 2843 assertNotNull(WebView.getWebViewClassLoader()); 2844 } 2845 } 2846