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