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