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 android.app.ActivityManager; 20 import android.graphics.Bitmap; 21 import android.os.Build; 22 import android.os.Message; 23 import android.platform.test.annotations.AppModeFull; 24 import android.test.ActivityInstrumentationTestCase2; 25 import android.view.KeyEvent; 26 import android.view.ViewGroup; 27 import android.webkit.HttpAuthHandler; 28 import android.webkit.RenderProcessGoneDetail; 29 import android.webkit.SafeBrowsingResponse; 30 import android.webkit.ValueCallback; 31 import android.webkit.WebChromeClient; 32 import android.webkit.WebResourceError; 33 import android.webkit.WebResourceRequest; 34 import android.webkit.WebResourceResponse; 35 import android.webkit.WebSettings; 36 import android.webkit.WebView; 37 import android.webkit.WebViewClient; 38 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient; 39 import android.util.Pair; 40 41 import com.android.compatibility.common.util.NullWebViewUtils; 42 import com.android.compatibility.common.util.PollingCheck; 43 import com.google.common.util.concurrent.SettableFuture; 44 45 import java.io.ByteArrayInputStream; 46 import java.nio.charset.StandardCharsets; 47 import java.util.HashMap; 48 import java.util.Map; 49 import java.util.List; 50 import java.util.ArrayList; 51 52 @AppModeFull 53 public class WebViewClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> { 54 private static final long TEST_TIMEOUT = 5000; 55 private static final String TEST_URL = "http://www.example.com/"; 56 57 private WebViewOnUiThread mOnUiThread; 58 private CtsTestServer mWebServer; 59 60 private static final String TEST_SAFE_BROWSING_URL_PREFIX = 61 "chrome://safe-browsing/match?type="; 62 private static final String TEST_SAFE_BROWSING_MALWARE_URL = 63 TEST_SAFE_BROWSING_URL_PREFIX + "malware"; 64 private static final String TEST_SAFE_BROWSING_PHISHING_URL = 65 TEST_SAFE_BROWSING_URL_PREFIX + "phishing"; 66 private static final String TEST_SAFE_BROWSING_UNWANTED_SOFTWARE_URL = 67 TEST_SAFE_BROWSING_URL_PREFIX + "unwanted"; 68 private static final String TEST_SAFE_BROWSING_BILLING_URL = 69 TEST_SAFE_BROWSING_URL_PREFIX + "billing"; 70 71 public WebViewClientTest() { 72 super("android.webkit.cts", WebViewCtsActivity.class); 73 } 74 75 @Override 76 protected void setUp() throws Exception { 77 super.setUp(); 78 final WebViewCtsActivity activity = getActivity(); 79 WebView webview = activity.getWebView(); 80 if (webview != null) { 81 new PollingCheck(TEST_TIMEOUT) { 82 @Override 83 protected boolean check() { 84 return activity.hasWindowFocus(); 85 } 86 }.run(); 87 88 mOnUiThread = new WebViewOnUiThread(webview); 89 } 90 } 91 92 @Override 93 protected void tearDown() throws Exception { 94 if (mOnUiThread != null) { 95 mOnUiThread.cleanUp(); 96 } 97 if (mWebServer != null) { 98 mWebServer.shutdown(); 99 } 100 super.tearDown(); 101 } 102 103 /** 104 * This should remain functionally equivalent to 105 * androidx.webkit.WebViewClientCompatTest#testShouldOverrideUrlLoadingDefault. Modifications 106 * to this test should be reflected in that test as necessary. See 107 * http://go/modifying-webview-cts. 108 */ 109 // Verify that the shouldoverrideurlloading is false by default 110 public void testShouldOverrideUrlLoadingDefault() { 111 if (!NullWebViewUtils.isWebViewAvailable()) { 112 return; 113 } 114 final WebViewClient webViewClient = new WebViewClient(); 115 assertFalse(webViewClient.shouldOverrideUrlLoading(mOnUiThread.getWebView(), new String())); 116 } 117 118 /** 119 * This should remain functionally equivalent to 120 * androidx.webkit.WebViewClientCompatTest#testShouldOverrideUrlLoading. Modifications to this 121 * test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 122 */ 123 // Verify shouldoverrideurlloading called on top level navigation 124 public void testShouldOverrideUrlLoading() { 125 if (!NullWebViewUtils.isWebViewAvailable()) { 126 return; 127 } 128 final MockWebViewClient webViewClient = new MockWebViewClient(); 129 mOnUiThread.setWebViewClient(webViewClient); 130 mOnUiThread.getSettings().setJavaScriptEnabled(true); 131 String data = "<html><body>" + 132 "<a href=\"" + TEST_URL + "\" id=\"link\">new page</a>" + 133 "</body></html>"; 134 mOnUiThread.loadDataAndWaitForCompletion(data, "text/html", null); 135 clickOnLinkUsingJs("link", mOnUiThread); 136 assertEquals(TEST_URL, webViewClient.getLastShouldOverrideUrl()); 137 assertNotNull(webViewClient.getLastShouldOverrideResourceRequest()); 138 assertTrue(webViewClient.getLastShouldOverrideResourceRequest().isForMainFrame()); 139 assertFalse(webViewClient.getLastShouldOverrideResourceRequest().isRedirect()); 140 assertFalse(webViewClient.getLastShouldOverrideResourceRequest().hasGesture()); 141 } 142 143 // Verify shouldoverrideurlloading called on webview called via onCreateWindow 144 // TODO(sgurun) upstream this test to Aw. 145 public void testShouldOverrideUrlLoadingOnCreateWindow() throws Exception { 146 if (!NullWebViewUtils.isWebViewAvailable()) { 147 return; 148 } 149 mWebServer = new CtsTestServer(getActivity()); 150 // WebViewClient for main window 151 final MockWebViewClient mainWebViewClient = new MockWebViewClient(); 152 // WebViewClient for child window 153 final MockWebViewClient childWebViewClient = new MockWebViewClient(); 154 mOnUiThread.setWebViewClient(mainWebViewClient); 155 mOnUiThread.getSettings().setJavaScriptEnabled(true); 156 mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); 157 mOnUiThread.getSettings().setSupportMultipleWindows(true); 158 159 final WebView childWebView = mOnUiThread.createWebView(); 160 161 WebViewOnUiThread childWebViewOnUiThread = new WebViewOnUiThread(childWebView); 162 mOnUiThread.setWebChromeClient(new WebChromeClient() { 163 @Override 164 public boolean onCreateWindow( 165 WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) { 166 WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj; 167 childWebView.setWebViewClient(childWebViewClient); 168 childWebView.getSettings().setJavaScriptEnabled(true); 169 transport.setWebView(childWebView); 170 getActivity().addContentView(childWebView, new ViewGroup.LayoutParams( 171 ViewGroup.LayoutParams.FILL_PARENT, 172 ViewGroup.LayoutParams.WRAP_CONTENT)); 173 resultMsg.sendToTarget(); 174 return true; 175 } 176 }); 177 { 178 final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount(); 179 mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.BLANK_TAG_URL)); 180 181 new PollingCheck(TEST_TIMEOUT) { 182 @Override 183 protected boolean check() { 184 return childWebViewClient.hasOnPageFinishedCalled(); 185 } 186 }.run(); 187 new PollingCheck(TEST_TIMEOUT) { 188 @Override 189 protected boolean check() { 190 return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount; 191 } 192 }.run(); 193 assertEquals(mWebServer.getAssetUrl(TestHtmlConstants.PAGE_WITH_LINK_URL), 194 childWebViewClient.getLastShouldOverrideUrl()); 195 } 196 197 final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount(); 198 final int mainCallCount = mainWebViewClient.getShouldOverrideUrlLoadingCallCount(); 199 clickOnLinkUsingJs("link", childWebViewOnUiThread); 200 new PollingCheck(TEST_TIMEOUT) { 201 @Override 202 protected boolean check() { 203 return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount; 204 } 205 }.run(); 206 assertEquals(mainCallCount, mainWebViewClient.getShouldOverrideUrlLoadingCallCount()); 207 assertEquals( 208 TestHtmlConstants.URL_IN_PAGE_WITH_LINK, childWebViewClient.getLastShouldOverrideUrl()); 209 } 210 211 private void clickOnLinkUsingJs(final String linkId, WebViewOnUiThread webViewOnUiThread) { 212 assertEquals("null", webViewOnUiThread.evaluateJavascriptSync( 213 "document.getElementById('" + linkId + "').click();" + 214 "console.log('element with id [" + linkId + "] clicked');")); 215 } 216 217 public void testLoadPage() throws Exception { 218 if (!NullWebViewUtils.isWebViewAvailable()) { 219 return; 220 } 221 final MockWebViewClient webViewClient = new MockWebViewClient(); 222 mOnUiThread.setWebViewClient(webViewClient); 223 mWebServer = new CtsTestServer(getActivity()); 224 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 225 226 assertFalse(webViewClient.hasOnPageStartedCalled()); 227 assertFalse(webViewClient.hasOnLoadResourceCalled()); 228 assertFalse(webViewClient.hasOnPageFinishedCalled()); 229 mOnUiThread.loadUrlAndWaitForCompletion(url); 230 231 new PollingCheck(TEST_TIMEOUT) { 232 @Override 233 protected boolean check() { 234 return webViewClient.hasOnPageStartedCalled(); 235 } 236 }.run(); 237 238 new PollingCheck(TEST_TIMEOUT) { 239 @Override 240 protected boolean check() { 241 return webViewClient.hasOnLoadResourceCalled(); 242 } 243 }.run(); 244 245 new PollingCheck(TEST_TIMEOUT) { 246 @Override 247 protected boolean check() { 248 return webViewClient.hasOnPageFinishedCalled(); 249 } 250 }.run(); 251 } 252 253 public void testOnReceivedLoginRequest() throws Exception { 254 if (!NullWebViewUtils.isWebViewAvailable()) { 255 return; 256 } 257 final MockWebViewClient webViewClient = new MockWebViewClient(); 258 mOnUiThread.setWebViewClient(webViewClient); 259 TestWebServer testServer = null; 260 //set the url and html 261 final String path = "/main"; 262 final String page = "<head></head><body>test onReceivedLoginRequest</body>"; 263 final String headerName = "x-auto-login"; 264 final String headerValue = "realm=com.google&account=foo%40bar.com&args=random_string"; 265 List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>(); 266 headers.add(Pair.create(headerName, headerValue)); 267 268 try { 269 testServer = new TestWebServer(false); 270 String url = testServer.setResponse(path, page, headers); 271 assertFalse(webViewClient.hasOnReceivedLoginRequest()); 272 mOnUiThread.loadUrlAndWaitForCompletion(url); 273 assertTrue(webViewClient.hasOnReceivedLoginRequest()); 274 new PollingCheck(TEST_TIMEOUT) { 275 @Override 276 protected boolean check() { 277 return webViewClient.hasOnReceivedLoginRequest(); 278 } 279 }.run(); 280 assertEquals("com.google", webViewClient.getLoginRequestRealm()); 281 assertEquals("foo (at) bar.com", webViewClient.getLoginRequestAccount()); 282 assertEquals("random_string", webViewClient.getLoginRequestArgs()); 283 } finally { 284 testServer.shutdown(); 285 } 286 } 287 /** 288 * This should remain functionally equivalent to 289 * androidx.webkit.WebViewClientCompatTest#testOnReceivedError. Modifications to this test 290 * should be reflected in that test as necessary. See http://go/modifying-webview-cts. 291 */ 292 public void testOnReceivedError() throws Exception { 293 if (!NullWebViewUtils.isWebViewAvailable()) { 294 return; 295 } 296 final MockWebViewClient webViewClient = new MockWebViewClient(); 297 mOnUiThread.setWebViewClient(webViewClient); 298 299 String wrongUri = "invalidscheme://some/resource"; 300 assertEquals(0, webViewClient.hasOnReceivedErrorCode()); 301 mOnUiThread.loadUrlAndWaitForCompletion(wrongUri); 302 assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME, 303 webViewClient.hasOnReceivedErrorCode()); 304 } 305 306 /** 307 * This should remain functionally equivalent to 308 * androidx.webkit.WebViewClientCompatTest#testOnReceivedErrorForSubresource. Modifications to 309 * this test should be reflected in that test as necessary. See http://go/modifying-webview-cts. 310 */ 311 public void testOnReceivedErrorForSubresource() throws Exception { 312 if (!NullWebViewUtils.isWebViewAvailable()) { 313 return; 314 } 315 final MockWebViewClient webViewClient = new MockWebViewClient(); 316 mOnUiThread.setWebViewClient(webViewClient); 317 mWebServer = new CtsTestServer(getActivity()); 318 319 assertNull(webViewClient.hasOnReceivedResourceError()); 320 String url = mWebServer.getAssetUrl(TestHtmlConstants.BAD_IMAGE_PAGE_URL); 321 mOnUiThread.loadUrlAndWaitForCompletion(url); 322 assertTrue(webViewClient.hasOnReceivedResourceError() != null); 323 assertEquals(WebViewClient.ERROR_UNSUPPORTED_SCHEME, 324 webViewClient.hasOnReceivedResourceError().getErrorCode()); 325 } 326 327 public void testOnReceivedHttpError() throws Exception { 328 if (!NullWebViewUtils.isWebViewAvailable()) { 329 return; 330 } 331 final MockWebViewClient webViewClient = new MockWebViewClient(); 332 mOnUiThread.setWebViewClient(webViewClient); 333 mWebServer = new CtsTestServer(getActivity()); 334 335 assertNull(webViewClient.hasOnReceivedHttpError()); 336 String url = mWebServer.getAssetUrl(TestHtmlConstants.NON_EXISTENT_PAGE_URL); 337 mOnUiThread.loadUrlAndWaitForCompletion(url); 338 assertTrue(webViewClient.hasOnReceivedHttpError() != null); 339 assertEquals(404, webViewClient.hasOnReceivedHttpError().getStatusCode()); 340 } 341 342 public void testOnFormResubmission() throws Exception { 343 if (!NullWebViewUtils.isWebViewAvailable()) { 344 return; 345 } 346 final MockWebViewClient webViewClient = new MockWebViewClient(); 347 mOnUiThread.setWebViewClient(webViewClient); 348 final WebSettings settings = mOnUiThread.getSettings(); 349 settings.setJavaScriptEnabled(true); 350 mWebServer = new CtsTestServer(getActivity()); 351 352 assertFalse(webViewClient.hasOnFormResubmissionCalled()); 353 String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_FORM_URL); 354 // this loads a form, which automatically posts itself 355 mOnUiThread.loadUrlAndWaitForCompletion(url); 356 // wait for JavaScript to post the form 357 mOnUiThread.waitForLoadCompletion(); 358 assertFalse("The URL should have changed when the form was posted", 359 url.equals(mOnUiThread.getUrl())); 360 // reloading the current URL should trigger the callback 361 mOnUiThread.reload(); 362 new PollingCheck(TEST_TIMEOUT) { 363 @Override 364 protected boolean check() { 365 return webViewClient.hasOnFormResubmissionCalled(); 366 } 367 }.run(); 368 } 369 370 public void testDoUpdateVisitedHistory() throws Exception { 371 if (!NullWebViewUtils.isWebViewAvailable()) { 372 return; 373 } 374 final MockWebViewClient webViewClient = new MockWebViewClient(); 375 mOnUiThread.setWebViewClient(webViewClient); 376 mWebServer = new CtsTestServer(getActivity()); 377 378 assertFalse(webViewClient.hasDoUpdateVisitedHistoryCalled()); 379 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 380 String url2 = mWebServer.getAssetUrl(TestHtmlConstants.BR_TAG_URL); 381 mOnUiThread.loadUrlAndWaitForCompletion(url1); 382 mOnUiThread.loadUrlAndWaitForCompletion(url2); 383 new PollingCheck(TEST_TIMEOUT) { 384 @Override 385 protected boolean check() { 386 return webViewClient.hasDoUpdateVisitedHistoryCalled(); 387 } 388 }.run(); 389 } 390 391 public void testOnReceivedHttpAuthRequest() throws Exception { 392 if (!NullWebViewUtils.isWebViewAvailable()) { 393 return; 394 } 395 final MockWebViewClient webViewClient = new MockWebViewClient(); 396 mOnUiThread.setWebViewClient(webViewClient); 397 mWebServer = new CtsTestServer(getActivity()); 398 399 assertFalse(webViewClient.hasOnReceivedHttpAuthRequestCalled()); 400 String url = mWebServer.getAuthAssetUrl(TestHtmlConstants.EMBEDDED_IMG_URL); 401 mOnUiThread.loadUrlAndWaitForCompletion(url); 402 assertTrue(webViewClient.hasOnReceivedHttpAuthRequestCalled()); 403 } 404 405 public void testShouldOverrideKeyEvent() { 406 if (!NullWebViewUtils.isWebViewAvailable()) { 407 return; 408 } 409 final MockWebViewClient webViewClient = new MockWebViewClient(); 410 mOnUiThread.setWebViewClient(webViewClient); 411 412 assertFalse(webViewClient.shouldOverrideKeyEvent(mOnUiThread.getWebView(), null)); 413 } 414 415 public void testOnUnhandledKeyEvent() throws Throwable { 416 if (!NullWebViewUtils.isWebViewAvailable()) { 417 return; 418 } 419 requireLoadedPage(); 420 final MockWebViewClient webViewClient = new MockWebViewClient(); 421 mOnUiThread.setWebViewClient(webViewClient); 422 423 mOnUiThread.requestFocus(); 424 getInstrumentation().waitForIdleSync(); 425 426 assertFalse(webViewClient.hasOnUnhandledKeyEventCalled()); 427 sendKeys(KeyEvent.KEYCODE_1); 428 429 new PollingCheck(TEST_TIMEOUT) { 430 @Override 431 protected boolean check() { 432 return webViewClient.hasOnUnhandledKeyEventCalled(); 433 } 434 }.run(); 435 } 436 437 public void testOnScaleChanged() throws Throwable { 438 if (!NullWebViewUtils.isWebViewAvailable()) { 439 return; 440 } 441 final MockWebViewClient webViewClient = new MockWebViewClient(); 442 mOnUiThread.setWebViewClient(webViewClient); 443 mWebServer = new CtsTestServer(getActivity()); 444 445 assertFalse(webViewClient.hasOnScaleChangedCalled()); 446 String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 447 mOnUiThread.loadUrlAndWaitForCompletion(url1); 448 449 new PollingCheck(TEST_TIMEOUT) { 450 @Override 451 protected boolean check() { 452 return mOnUiThread.canZoomIn(); 453 } 454 }.run(); 455 456 assertTrue(mOnUiThread.zoomIn()); 457 new PollingCheck(TEST_TIMEOUT) { 458 @Override 459 protected boolean check() { 460 return webViewClient.hasOnScaleChangedCalled(); 461 } 462 }.run(); 463 } 464 465 // Test that shouldInterceptRequest is called with the correct parameters 466 public void testShouldInterceptRequestParams() throws Throwable { 467 if (!NullWebViewUtils.isWebViewAvailable()) { 468 return; 469 } 470 471 final String mainPath = "/main"; 472 final String mainPage = "<head></head><body>test page</body>"; 473 final String headerName = "x-test-header-name"; 474 final String headerValue = "testheadervalue"; 475 HashMap<String, String> headers = new HashMap<String, String>(1); 476 headers.put(headerName, headerValue); 477 478 // A client which saves the WebResourceRequest as interceptRequest 479 final class TestClient extends WaitForLoadedClient { 480 public TestClient() { 481 super(mOnUiThread); 482 } 483 484 @Override 485 public WebResourceResponse shouldInterceptRequest(WebView view, 486 WebResourceRequest request) { 487 assertNotNull(view); 488 assertNotNull(request); 489 490 assertEquals(view, mOnUiThread.getWebView()); 491 492 // Save the main page request; discard any other requests (e.g. for favicon.ico) 493 if (request.getUrl().getPath().equals(mainPath)) { 494 assertNull(interceptRequest); 495 interceptRequest = request; 496 } 497 498 return null; 499 } 500 501 public volatile WebResourceRequest interceptRequest; 502 } 503 504 TestClient client = new TestClient(); 505 mOnUiThread.setWebViewClient(client); 506 507 TestWebServer server = new TestWebServer(false); 508 try { 509 String mainUrl = server.setResponse(mainPath, mainPage, null); 510 511 mOnUiThread.loadUrlAndWaitForCompletion(mainUrl, headers); 512 513 // Inspect the fields of the saved WebResourceRequest 514 assertNotNull(client.interceptRequest); 515 assertEquals(mainUrl, client.interceptRequest.getUrl().toString()); 516 assertTrue(client.interceptRequest.isForMainFrame()); 517 assertEquals(server.getLastRequest(mainPath).getRequestLine().getMethod(), 518 client.interceptRequest.getMethod()); 519 520 // Web request headers are case-insensitive. We provided lower-case headerName and 521 // headerValue. This will pass implementations which either do not mangle case, 522 // convert to lowercase, or convert to uppercase but return a case-insensitive map. 523 Map<String, String> interceptHeaders = client.interceptRequest.getRequestHeaders(); 524 assertTrue(interceptHeaders.containsKey(headerName)); 525 assertEquals(headerValue, interceptHeaders.get(headerName)); 526 } finally { 527 server.shutdown(); 528 } 529 } 530 531 // Test that the WebResourceResponse returned by shouldInterceptRequest is handled correctly 532 public void testShouldInterceptRequestResponse() throws Throwable { 533 if (!NullWebViewUtils.isWebViewAvailable()) { 534 return; 535 } 536 537 final String mainPath = "/main"; 538 final String mainPage = "<head></head><body>test page</body>"; 539 final String interceptPath = "/intercept_me"; 540 541 // A client which responds to requests for interceptPath with a saved interceptResponse 542 final class TestClient extends WaitForLoadedClient { 543 public TestClient() { 544 super(mOnUiThread); 545 } 546 547 @Override 548 public WebResourceResponse shouldInterceptRequest(WebView view, 549 WebResourceRequest request) { 550 if (request.getUrl().toString().contains(interceptPath)) { 551 assertNotNull(interceptResponse); 552 return interceptResponse; 553 } 554 555 return null; 556 } 557 558 volatile public WebResourceResponse interceptResponse; 559 } 560 561 mOnUiThread.getSettings().setJavaScriptEnabled(true); 562 563 TestClient client = new TestClient(); 564 mOnUiThread.setWebViewClient(client); 565 566 TestWebServer server = new TestWebServer(false); 567 try { 568 String interceptUrl = server.getResponseUrl(interceptPath); 569 570 // JavaScript which makes a synchronous AJAX request and logs and returns the status 571 String js = 572 "(function() {" + 573 " var xhr = new XMLHttpRequest();" + 574 " xhr.open('GET', '" + interceptUrl + "', false);" + 575 " xhr.send(null);" + 576 " console.info('xhr.status = ' + xhr.status);" + 577 " console.info('xhr.statusText = ' + xhr.statusText);" + 578 " return '[' + xhr.status + '][' + xhr.statusText + ']';" + 579 "})();"; 580 581 String mainUrl = server.setResponse(mainPath, mainPage, null); 582 mOnUiThread.loadUrlAndWaitForCompletion(mainUrl, null); 583 584 // Test a nonexistent page 585 client.interceptResponse = new WebResourceResponse("text/html", "UTF-8", null); 586 assertEquals("\"[404][Not Found]\"", mOnUiThread.evaluateJavascriptSync(js)); 587 588 // Test an empty page 589 client.interceptResponse = new WebResourceResponse("text/html", "UTF-8", 590 new ByteArrayInputStream(new byte[0])); 591 assertEquals("\"[200][OK]\"", mOnUiThread.evaluateJavascriptSync(js)); 592 593 // Test a nonempty page with unusual response code/text 594 client.interceptResponse = 595 new WebResourceResponse("text/html", "UTF-8", 123, "unusual", null, 596 new ByteArrayInputStream("nonempty page".getBytes(StandardCharsets.UTF_8))); 597 assertEquals("\"[123][unusual]\"", mOnUiThread.evaluateJavascriptSync(js)); 598 } finally { 599 server.shutdown(); 600 } 601 } 602 603 // Verify that OnRenderProcessGone returns false by default 604 public void testOnRenderProcessGoneDefault() throws Throwable { 605 if (!NullWebViewUtils.isWebViewAvailable()) { 606 return; 607 } 608 final WebViewClient webViewClient = new WebViewClient(); 609 assertFalse(webViewClient.onRenderProcessGone(mOnUiThread.getWebView(), null)); 610 } 611 612 public void testOnRenderProcessGone() throws Throwable { 613 if (!NullWebViewUtils.isWebViewAvailable()) { 614 return; 615 } 616 if (!getActivity().isMultiprocessMode()) { 617 return; 618 } 619 final MockWebViewClient webViewClient = new MockWebViewClient(); 620 mOnUiThread.setWebViewClient(webViewClient); 621 mOnUiThread.loadUrl("chrome://kill"); 622 new PollingCheck(TEST_TIMEOUT * 5) { 623 @Override 624 protected boolean check() { 625 return webViewClient.hasRenderProcessGoneCalled(); 626 } 627 }.run(); 628 assertFalse(webViewClient.didRenderProcessCrash()); 629 } 630 631 /** 632 * This should remain functionally equivalent to 633 * androidx.webkit.WebViewClientCompatTest#testOnSafeBrowsingHitBackToSafety. 634 * Modifications to this test should be reflected in that test as necessary. See 635 * http://go/modifying-webview-cts. 636 */ 637 public void testOnSafeBrowsingHitBackToSafety() throws Throwable { 638 if (!NullWebViewUtils.isWebViewAvailable()) { 639 return; 640 } 641 mWebServer = new CtsTestServer(getActivity()); 642 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 643 mOnUiThread.loadUrlAndWaitForCompletion(url); 644 final String ORIGINAL_URL = mOnUiThread.getUrl(); 645 646 final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient = 647 new SafeBrowsingBackToSafetyClient(); 648 mOnUiThread.setWebViewClient(backToSafetyWebViewClient); 649 mOnUiThread.getSettings().setSafeBrowsingEnabled(true); 650 651 // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually 652 // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is 653 // enabled. 654 if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) { 655 assertEquals(0, backToSafetyWebViewClient.hasOnReceivedErrorCode()); 656 mOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_MALWARE_URL); 657 658 assertEquals(TEST_SAFE_BROWSING_MALWARE_URL, 659 backToSafetyWebViewClient.getOnSafeBrowsingHitRequest().getUrl().toString()); 660 assertTrue(backToSafetyWebViewClient.getOnSafeBrowsingHitRequest().isForMainFrame()); 661 662 assertEquals("Back to safety should produce a network error", 663 WebViewClient.ERROR_UNSAFE_RESOURCE, 664 backToSafetyWebViewClient.hasOnReceivedErrorCode()); 665 666 assertEquals("Back to safety should navigate backward", ORIGINAL_URL, 667 mOnUiThread.getUrl()); 668 } 669 } 670 671 /** 672 * This should remain functionally equivalent to 673 * androidx.webkit.WebViewClientCompatTest#testOnSafeBrowsingHitProceed. 674 * Modifications to this test should be reflected in that test as necessary. See 675 * http://go/modifying-webview-cts. 676 */ 677 public void testOnSafeBrowsingHitProceed() throws Throwable { 678 if (!NullWebViewUtils.isWebViewAvailable()) { 679 return; 680 } 681 mWebServer = new CtsTestServer(getActivity()); 682 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 683 mOnUiThread.loadUrlAndWaitForCompletion(url); 684 final String ORIGINAL_URL = mOnUiThread.getUrl(); 685 686 final SafeBrowsingProceedClient proceedWebViewClient = 687 new SafeBrowsingProceedClient(); 688 mOnUiThread.setWebViewClient(proceedWebViewClient); 689 mOnUiThread.getSettings().setSafeBrowsingEnabled(true); 690 691 // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually 692 // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is 693 // enabled. 694 if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) { 695 assertEquals(0, proceedWebViewClient.hasOnReceivedErrorCode()); 696 mOnUiThread.loadUrlAndWaitForCompletion(TEST_SAFE_BROWSING_MALWARE_URL); 697 698 assertEquals(TEST_SAFE_BROWSING_MALWARE_URL, 699 proceedWebViewClient.getOnSafeBrowsingHitRequest().getUrl().toString()); 700 assertTrue(proceedWebViewClient.getOnSafeBrowsingHitRequest().isForMainFrame()); 701 702 assertEquals("Proceed button should navigate to the page", 703 TEST_SAFE_BROWSING_MALWARE_URL, mOnUiThread.getUrl()); 704 } 705 } 706 707 private void testOnSafeBrowsingCode(String expectedUrl, int expectedThreatType) 708 throws Throwable { 709 if (!NullWebViewUtils.isWebViewAvailable()) { 710 return; 711 } 712 mWebServer = new CtsTestServer(getActivity()); 713 String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL); 714 mOnUiThread.loadUrlAndWaitForCompletion(url); 715 final String ORIGINAL_URL = mOnUiThread.getUrl(); 716 717 final SafeBrowsingBackToSafetyClient backToSafetyWebViewClient = 718 new SafeBrowsingBackToSafetyClient(); 719 mOnUiThread.setWebViewClient(backToSafetyWebViewClient); 720 mOnUiThread.getSettings().setSafeBrowsingEnabled(true); 721 722 // Note: Safe Browsing depends on user opt-in as well, so we can't assume it's actually 723 // enabled. #getSafeBrowsingEnabled will tell us the true state of whether Safe Browsing is 724 // enabled. 725 if (mOnUiThread.getSettings().getSafeBrowsingEnabled()) { 726 mOnUiThread.loadUrlAndWaitForCompletion(expectedUrl); 727 728 assertEquals("Safe Browsing hit is for unexpected URL", 729 expectedUrl, 730 backToSafetyWebViewClient.getOnSafeBrowsingHitRequest().getUrl().toString()); 731 732 assertEquals("Safe Browsing hit has unexpected threat type", 733 expectedThreatType, 734 backToSafetyWebViewClient.getOnSafeBrowsingHitThreatType()); 735 } 736 } 737 738 public void testOnSafeBrowsingMalwareCode() throws Throwable { 739 testOnSafeBrowsingCode(TEST_SAFE_BROWSING_MALWARE_URL, 740 WebViewClient.SAFE_BROWSING_THREAT_MALWARE); 741 } 742 743 public void testOnSafeBrowsingPhishingCode() throws Throwable { 744 testOnSafeBrowsingCode(TEST_SAFE_BROWSING_PHISHING_URL, 745 WebViewClient.SAFE_BROWSING_THREAT_PHISHING); 746 } 747 748 public void testOnSafeBrowsingUnwantedSoftwareCode() throws Throwable { 749 testOnSafeBrowsingCode(TEST_SAFE_BROWSING_UNWANTED_SOFTWARE_URL, 750 WebViewClient.SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE); 751 } 752 753 public void testOnSafeBrowsingBillingCode() throws Throwable { 754 testOnSafeBrowsingCode(TEST_SAFE_BROWSING_BILLING_URL, 755 WebViewClient.SAFE_BROWSING_THREAT_BILLING); 756 } 757 758 private void requireLoadedPage() throws Throwable { 759 if (!NullWebViewUtils.isWebViewAvailable()) { 760 return; 761 } 762 mOnUiThread.loadUrlAndWaitForCompletion("about:blank"); 763 } 764 765 /** 766 * This should remain functionally equivalent to 767 * androidx.webkit.WebViewClientCompatTest#testOnPageCommitVisibleCalled. 768 * Modifications to this test should be reflected in that test as necessary. See 769 * http://go/modifying-webview-cts. 770 */ 771 public void testOnPageCommitVisibleCalled() throws Exception { 772 // Check that the onPageCommitVisible callback is called 773 // correctly. 774 if (!NullWebViewUtils.isWebViewAvailable()) { 775 return; 776 } 777 778 final SettableFuture<String> pageCommitVisibleFuture = SettableFuture.create(); 779 mOnUiThread.setWebViewClient(new WebViewClient() { 780 public void onPageCommitVisible(WebView view, String url) { 781 pageCommitVisibleFuture.set(url); 782 } 783 }); 784 785 final String url = "about:blank"; 786 mOnUiThread.loadUrl(url); 787 assertEquals(url, WebkitUtils.waitForFuture(pageCommitVisibleFuture)); 788 } 789 790 private class MockWebViewClient extends WaitForLoadedClient { 791 private boolean mOnPageStartedCalled; 792 private boolean mOnPageFinishedCalled; 793 private boolean mOnLoadResourceCalled; 794 private int mOnReceivedErrorCode; 795 private WebResourceError mOnReceivedResourceError; 796 private WebResourceResponse mOnReceivedHttpError; 797 private boolean mOnFormResubmissionCalled; 798 private boolean mDoUpdateVisitedHistoryCalled; 799 private boolean mOnReceivedHttpAuthRequestCalled; 800 private boolean mOnReceivedLoginRequest; 801 private String mOnReceivedLoginAccount; 802 private String mOnReceivedLoginArgs; 803 private String mOnReceivedLoginRealm; 804 private boolean mOnUnhandledKeyEventCalled; 805 private boolean mOnScaleChangedCalled; 806 private int mShouldOverrideUrlLoadingCallCount; 807 private String mLastShouldOverrideUrl; 808 private WebResourceRequest mLastShouldOverrideResourceRequest; 809 private boolean mOnRenderProcessGoneCalled; 810 private boolean mRenderProcessCrashed; 811 812 public MockWebViewClient() { 813 super(mOnUiThread); 814 } 815 816 public boolean hasOnPageStartedCalled() { 817 return mOnPageStartedCalled; 818 } 819 820 public boolean hasOnPageFinishedCalled() { 821 return mOnPageFinishedCalled; 822 } 823 824 public boolean hasOnLoadResourceCalled() { 825 return mOnLoadResourceCalled; 826 } 827 828 public int hasOnReceivedErrorCode() { 829 return mOnReceivedErrorCode; 830 } 831 832 public boolean hasOnReceivedLoginRequest() { 833 return mOnReceivedLoginRequest; 834 } 835 836 public WebResourceError hasOnReceivedResourceError() { 837 return mOnReceivedResourceError; 838 } 839 840 public WebResourceResponse hasOnReceivedHttpError() { 841 return mOnReceivedHttpError; 842 } 843 844 public boolean hasOnFormResubmissionCalled() { 845 return mOnFormResubmissionCalled; 846 } 847 848 public boolean hasDoUpdateVisitedHistoryCalled() { 849 return mDoUpdateVisitedHistoryCalled; 850 } 851 852 public boolean hasOnReceivedHttpAuthRequestCalled() { 853 return mOnReceivedHttpAuthRequestCalled; 854 } 855 856 public boolean hasOnUnhandledKeyEventCalled() { 857 return mOnUnhandledKeyEventCalled; 858 } 859 860 public boolean hasOnScaleChangedCalled() { 861 return mOnScaleChangedCalled; 862 } 863 864 public int getShouldOverrideUrlLoadingCallCount() { 865 return mShouldOverrideUrlLoadingCallCount; 866 } 867 868 public String getLastShouldOverrideUrl() { 869 return mLastShouldOverrideUrl; 870 } 871 872 public WebResourceRequest getLastShouldOverrideResourceRequest() { 873 return mLastShouldOverrideResourceRequest; 874 } 875 876 public String getLoginRequestRealm() { 877 return mOnReceivedLoginRealm; 878 } 879 880 public String getLoginRequestAccount() { 881 return mOnReceivedLoginAccount; 882 } 883 884 public String getLoginRequestArgs() { 885 return mOnReceivedLoginArgs; 886 } 887 888 public boolean hasRenderProcessGoneCalled() { 889 return mOnRenderProcessGoneCalled; 890 } 891 892 public boolean didRenderProcessCrash() { 893 return mRenderProcessCrashed; 894 } 895 896 @Override 897 public void onPageStarted(WebView view, String url, Bitmap favicon) { 898 super.onPageStarted(view, url, favicon); 899 mOnPageStartedCalled = true; 900 } 901 902 @Override 903 public void onPageFinished(WebView view, String url) { 904 super.onPageFinished(view, url); 905 assertTrue(mOnPageStartedCalled); 906 assertTrue(mOnLoadResourceCalled); 907 mOnPageFinishedCalled = true; 908 } 909 910 @Override 911 public void onLoadResource(WebView view, String url) { 912 super.onLoadResource(view, url); 913 mOnLoadResourceCalled = true; 914 } 915 916 @Override 917 public void onReceivedError(WebView view, int errorCode, 918 String description, String failingUrl) { 919 super.onReceivedError(view, errorCode, description, failingUrl); 920 mOnReceivedErrorCode = errorCode; 921 } 922 923 @Override 924 public void onReceivedError(WebView view, WebResourceRequest request, 925 WebResourceError error) { 926 super.onReceivedError(view, request, error); 927 mOnReceivedResourceError = error; 928 } 929 930 @Override 931 public void onReceivedHttpError(WebView view, WebResourceRequest request, 932 WebResourceResponse errorResponse) { 933 super.onReceivedHttpError(view, request, errorResponse); 934 mOnReceivedHttpError = errorResponse; 935 } 936 937 @Override 938 public void onReceivedLoginRequest(WebView view, String realm, String account, 939 String args) { 940 super.onReceivedLoginRequest(view, realm, account, args); 941 mOnReceivedLoginRequest = true; 942 mOnReceivedLoginRealm = realm; 943 mOnReceivedLoginAccount = account; 944 mOnReceivedLoginArgs = args; 945 } 946 947 @Override 948 public void onFormResubmission(WebView view, Message dontResend, Message resend) { 949 mOnFormResubmissionCalled = true; 950 dontResend.sendToTarget(); 951 } 952 953 @Override 954 public void doUpdateVisitedHistory(WebView view, String url, boolean isReload) { 955 super.doUpdateVisitedHistory(view, url, isReload); 956 mDoUpdateVisitedHistoryCalled = true; 957 } 958 959 @Override 960 public void onReceivedHttpAuthRequest(WebView view, 961 HttpAuthHandler handler, String host, String realm) { 962 super.onReceivedHttpAuthRequest(view, handler, host, realm); 963 mOnReceivedHttpAuthRequestCalled = true; 964 } 965 966 @Override 967 public void onUnhandledKeyEvent(WebView view, KeyEvent event) { 968 super.onUnhandledKeyEvent(view, event); 969 mOnUnhandledKeyEventCalled = true; 970 } 971 972 @Override 973 public void onScaleChanged(WebView view, float oldScale, float newScale) { 974 super.onScaleChanged(view, oldScale, newScale); 975 mOnScaleChangedCalled = true; 976 } 977 978 @Override 979 public boolean shouldOverrideUrlLoading(WebView view, String url) { 980 mLastShouldOverrideUrl = url; 981 mLastShouldOverrideResourceRequest = null; 982 mShouldOverrideUrlLoadingCallCount++; 983 return false; 984 } 985 986 @Override 987 public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { 988 mLastShouldOverrideUrl = request.getUrl().toString(); 989 mLastShouldOverrideResourceRequest = request; 990 mShouldOverrideUrlLoadingCallCount++; 991 return false; 992 } 993 994 @Override 995 public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) { 996 mOnRenderProcessGoneCalled = true; 997 mRenderProcessCrashed = detail.didCrash(); 998 return true; 999 } 1000 } 1001 1002 private class SafeBrowsingBackToSafetyClient extends MockWebViewClient { 1003 private WebResourceRequest mOnSafeBrowsingHitRequest; 1004 private int mOnSafeBrowsingHitThreatType; 1005 1006 public WebResourceRequest getOnSafeBrowsingHitRequest() { 1007 return mOnSafeBrowsingHitRequest; 1008 } 1009 1010 public int getOnSafeBrowsingHitThreatType() { 1011 return mOnSafeBrowsingHitThreatType; 1012 } 1013 1014 @Override 1015 public void onSafeBrowsingHit(WebView view, WebResourceRequest request, 1016 int threatType, SafeBrowsingResponse response) { 1017 // Immediately go back to safety to return the network error code 1018 mOnSafeBrowsingHitRequest = request; 1019 mOnSafeBrowsingHitThreatType = threatType; 1020 response.backToSafety(/* report */ true); 1021 } 1022 } 1023 1024 private class SafeBrowsingProceedClient extends MockWebViewClient { 1025 private WebResourceRequest mOnSafeBrowsingHitRequest; 1026 private int mOnSafeBrowsingHitThreatType; 1027 1028 public WebResourceRequest getOnSafeBrowsingHitRequest() { 1029 return mOnSafeBrowsingHitRequest; 1030 } 1031 1032 public int getOnSafeBrowsingHitThreatType() { 1033 return mOnSafeBrowsingHitThreatType; 1034 } 1035 1036 @Override 1037 public void onSafeBrowsingHit(WebView view, WebResourceRequest request, 1038 int threatType, SafeBrowsingResponse response) { 1039 // Proceed through Safe Browsing warnings 1040 mOnSafeBrowsingHitRequest = request; 1041 mOnSafeBrowsingHitThreatType = threatType; 1042 response.proceed(/* report */ true); 1043 } 1044 } 1045 } 1046