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