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