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