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