1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.android_webview.test; 6 7import android.graphics.Bitmap; 8import android.graphics.Canvas; 9import android.graphics.Color; 10import android.test.suitebuilder.annotation.SmallTest; 11import android.util.Pair; 12 13import org.chromium.android_webview.AwContents; 14import org.chromium.android_webview.AwContentsClient.ShouldInterceptRequestParams; 15import org.chromium.android_webview.AwWebResourceResponse; 16import org.chromium.android_webview.test.util.AwTestTouchUtils; 17import org.chromium.android_webview.test.util.CommonResources; 18import org.chromium.android_webview.test.util.JSUtils; 19import org.chromium.base.test.util.Feature; 20import org.chromium.base.test.util.TestFileUtil; 21import org.chromium.content.browser.test.util.CallbackHelper; 22import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnReceivedErrorHelper; 23import org.chromium.net.test.util.TestWebServer; 24 25import java.io.ByteArrayInputStream; 26import java.io.IOException; 27import java.io.InputStream; 28import java.util.ArrayList; 29import java.util.HashMap; 30import java.util.List; 31import java.util.Map; 32import java.util.concurrent.Callable; 33import java.util.concurrent.ConcurrentHashMap; 34import java.util.concurrent.CountDownLatch; 35 36/** 37 * Tests for the WebViewClient.shouldInterceptRequest() method. 38 */ 39public class AwContentsClientShouldInterceptRequestTest extends AwTestBase { 40 41 private static class TestAwContentsClient 42 extends org.chromium.android_webview.test.TestAwContentsClient { 43 44 public static class ShouldInterceptRequestHelper extends CallbackHelper { 45 private List<String> mShouldInterceptRequestUrls = new ArrayList<String>(); 46 private ConcurrentHashMap<String, AwWebResourceResponse> mReturnValuesByUrls 47 = new ConcurrentHashMap<String, AwWebResourceResponse>(); 48 private ConcurrentHashMap<String, ShouldInterceptRequestParams> mParamsByUrls 49 = new ConcurrentHashMap<String, ShouldInterceptRequestParams>(); 50 // This is read from the IO thread, so needs to be marked volatile. 51 private volatile AwWebResourceResponse mShouldInterceptRequestReturnValue = null; 52 void setReturnValue(AwWebResourceResponse value) { 53 mShouldInterceptRequestReturnValue = value; 54 } 55 void setReturnValueForUrl(String url, AwWebResourceResponse value) { 56 mReturnValuesByUrls.put(url, value); 57 } 58 public List<String> getUrls() { 59 assert getCallCount() > 0; 60 return mShouldInterceptRequestUrls; 61 } 62 public AwWebResourceResponse getReturnValue(String url) { 63 AwWebResourceResponse value = mReturnValuesByUrls.get(url); 64 if (value != null) return value; 65 return mShouldInterceptRequestReturnValue; 66 } 67 public ShouldInterceptRequestParams getParamsForUrl(String url) { 68 assert getCallCount() > 0; 69 assert mParamsByUrls.containsKey(url); 70 return mParamsByUrls.get(url); 71 } 72 public void notifyCalled(ShouldInterceptRequestParams params) { 73 mShouldInterceptRequestUrls.add(params.url); 74 mParamsByUrls.put(params.url, params); 75 notifyCalled(); 76 } 77 } 78 79 public static class OnLoadResourceHelper extends CallbackHelper { 80 private String mUrl; 81 82 public String getUrl() { 83 assert getCallCount() > 0; 84 return mUrl; 85 } 86 87 public void notifyCalled(String url) { 88 mUrl = url; 89 notifyCalled(); 90 } 91 } 92 93 @Override 94 public AwWebResourceResponse shouldInterceptRequest(ShouldInterceptRequestParams params) { 95 AwWebResourceResponse returnValue = 96 mShouldInterceptRequestHelper.getReturnValue(params.url); 97 mShouldInterceptRequestHelper.notifyCalled(params); 98 return returnValue; 99 } 100 101 @Override 102 public void onLoadResource(String url) { 103 super.onLoadResource(url); 104 mOnLoadResourceHelper.notifyCalled(url); 105 } 106 107 private ShouldInterceptRequestHelper mShouldInterceptRequestHelper; 108 private OnLoadResourceHelper mOnLoadResourceHelper; 109 110 public TestAwContentsClient() { 111 mShouldInterceptRequestHelper = new ShouldInterceptRequestHelper(); 112 mOnLoadResourceHelper = new OnLoadResourceHelper(); 113 } 114 115 public ShouldInterceptRequestHelper getShouldInterceptRequestHelper() { 116 return mShouldInterceptRequestHelper; 117 } 118 119 public OnLoadResourceHelper getOnLoadResourceHelper() { 120 return mOnLoadResourceHelper; 121 } 122 } 123 124 private static final int TEAPOT_STATUS_CODE = 418; 125 private static final String TEAPOT_RESPONSE_PHRASE = "I'm a teapot"; 126 127 private String addPageToTestServer(TestWebServer webServer, String httpPath, String html) { 128 List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>(); 129 headers.add(Pair.create("Content-Type", "text/html")); 130 headers.add(Pair.create("Cache-Control", "no-store")); 131 return webServer.setResponse(httpPath, html, headers); 132 } 133 134 private String addAboutPageToTestServer(TestWebServer webServer) { 135 return addPageToTestServer(webServer, "/" + CommonResources.ABOUT_FILENAME, 136 CommonResources.ABOUT_HTML); 137 } 138 139 private AwWebResourceResponse stringToAwWebResourceResponse(String input) throws Throwable { 140 final String mimeType = "text/html"; 141 final String encoding = "UTF-8"; 142 143 return new AwWebResourceResponse( 144 mimeType, encoding, new ByteArrayInputStream(input.getBytes(encoding))); 145 } 146 147 private TestWebServer mWebServer; 148 private TestAwContentsClient mContentsClient; 149 private AwTestContainerView mTestContainerView; 150 private AwContents mAwContents; 151 private TestAwContentsClient.ShouldInterceptRequestHelper mShouldInterceptRequestHelper; 152 153 @Override 154 protected void setUp() throws Exception { 155 super.setUp(); 156 157 mContentsClient = new TestAwContentsClient(); 158 mTestContainerView = createAwTestContainerViewOnMainSync(mContentsClient); 159 mAwContents = mTestContainerView.getAwContents(); 160 mShouldInterceptRequestHelper = mContentsClient.getShouldInterceptRequestHelper(); 161 162 mWebServer = new TestWebServer(false); 163 } 164 165 @Override 166 protected void tearDown() throws Exception { 167 mWebServer.shutdown(); 168 super.tearDown(); 169 } 170 171 @SmallTest 172 @Feature({"AndroidWebView"}) 173 public void testCalledWithCorrectUrlParam() throws Throwable { 174 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 175 176 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); 177 178 int callCount = mShouldInterceptRequestHelper.getCallCount(); 179 loadUrlAsync(mAwContents, aboutPageUrl); 180 mShouldInterceptRequestHelper.waitForCallback(callCount); 181 assertEquals(1, mShouldInterceptRequestHelper.getUrls().size()); 182 assertEquals(aboutPageUrl, 183 mShouldInterceptRequestHelper.getUrls().get(0)); 184 185 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount); 186 assertEquals(CommonResources.ABOUT_TITLE, getTitleOnUiThread(mAwContents)); 187 } 188 189 @SmallTest 190 @Feature({"AndroidWebView"}) 191 public void testCalledWithCorrectIsMainFrameParam() throws Throwable { 192 final String subframeUrl = addAboutPageToTestServer(mWebServer); 193 final String pageWithIframeUrl = addPageToTestServer(mWebServer, "/page_with_iframe.html", 194 CommonResources.makeHtmlPageFrom("", 195 "<iframe src=\"" + subframeUrl + "\"/>")); 196 197 int callCount = mShouldInterceptRequestHelper.getCallCount(); 198 loadUrlAsync(mAwContents, pageWithIframeUrl); 199 mShouldInterceptRequestHelper.waitForCallback(callCount, 2); 200 assertEquals(2, mShouldInterceptRequestHelper.getUrls().size()); 201 assertEquals(false, 202 mShouldInterceptRequestHelper.getParamsForUrl(subframeUrl).isMainFrame); 203 assertEquals(true, 204 mShouldInterceptRequestHelper.getParamsForUrl(pageWithIframeUrl).isMainFrame); 205 } 206 207 @SmallTest 208 @Feature({"AndroidWebView"}) 209 public void testCalledWithCorrectMethodParam() throws Throwable { 210 final String pageToPostToUrl = addAboutPageToTestServer(mWebServer); 211 final String pageWithFormUrl = addPageToTestServer(mWebServer, "/page_with_form.html", 212 CommonResources.makeHtmlPageWithSimplePostFormTo(pageToPostToUrl)); 213 enableJavaScriptOnUiThread(mAwContents); 214 215 int callCount = mShouldInterceptRequestHelper.getCallCount(); 216 loadUrlAsync(mAwContents, pageWithFormUrl); 217 mShouldInterceptRequestHelper.waitForCallback(callCount); 218 assertEquals("GET", 219 mShouldInterceptRequestHelper.getParamsForUrl(pageWithFormUrl).method); 220 221 callCount = mShouldInterceptRequestHelper.getCallCount(); 222 JSUtils.clickOnLinkUsingJs(this, mAwContents, 223 mContentsClient.getOnEvaluateJavaScriptResultHelper(), "link"); 224 mShouldInterceptRequestHelper.waitForCallback(callCount); 225 assertEquals("POST", 226 mShouldInterceptRequestHelper.getParamsForUrl(pageToPostToUrl).method); 227 } 228 229 @SmallTest 230 @Feature({"AndroidWebView"}) 231 public void testCalledWithCorrectHasUserGestureParam() throws Throwable { 232 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 233 final String pageWithLinkUrl = addPageToTestServer(mWebServer, "/page_with_link.html", 234 CommonResources.makeHtmlPageWithSimpleLinkTo(aboutPageUrl)); 235 enableJavaScriptOnUiThread(mAwContents); 236 237 int callCount = mShouldInterceptRequestHelper.getCallCount(); 238 loadUrlAsync(mAwContents, pageWithLinkUrl); 239 mShouldInterceptRequestHelper.waitForCallback(callCount); 240 assertEquals(false, 241 mShouldInterceptRequestHelper.getParamsForUrl(pageWithLinkUrl).hasUserGesture); 242 243 // TODO(mkosiba): Remove this once we have a real API to wait for the page to load and 244 // display. 245 // http://crbug.com/364612 246 // 247 // The code here is waiting for the "link" (which is a full-screen blue div) to appear on 248 // screen. 249 pollOnUiThread(new Callable<Boolean>() { 250 @Override 251 public Boolean call() throws Exception { 252 Bitmap bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888); 253 Canvas canvas = new Canvas(bitmap); 254 canvas.translate(-(float) mTestContainerView.getWidth() / 2, 255 -(float) mTestContainerView.getHeight() / 2); 256 mAwContents.onDraw(canvas); 257 return bitmap.getPixel(0, 0) == Color.BLUE; 258 } 259 }); 260 callCount = mShouldInterceptRequestHelper.getCallCount(); 261 AwTestTouchUtils.simulateTouchCenterOfView(mTestContainerView); 262 mShouldInterceptRequestHelper.waitForCallback(callCount); 263 assertEquals(true, 264 mShouldInterceptRequestHelper.getParamsForUrl(aboutPageUrl).hasUserGesture); 265 } 266 267 @SmallTest 268 @Feature({"AndroidWebView"}) 269 public void testCalledWithCorrectHeadersParam() throws Throwable { 270 final String headerName = "X-Test-Header-Name"; 271 final String headerValue = "TestHeaderValue"; 272 final String syncGetUrl = addPageToTestServer(mWebServer, "/intercept_me", 273 CommonResources.ABOUT_HTML); 274 final String mainPageUrl = addPageToTestServer(mWebServer, "/main", 275 CommonResources.makeHtmlPageFrom("", 276 "<script>" + 277 " var xhr = new XMLHttpRequest();" + 278 " xhr.open('GET', '" + syncGetUrl + "', false);" + 279 " xhr.setRequestHeader('" + headerName + "', '" + headerValue + "'); " + 280 " xhr.send(null);" + 281 "</script>")); 282 enableJavaScriptOnUiThread(mAwContents); 283 284 int callCount = mShouldInterceptRequestHelper.getCallCount(); 285 loadUrlAsync(mAwContents, mainPageUrl); 286 mShouldInterceptRequestHelper.waitForCallback(callCount, 2); 287 288 Map<String, String> headers = 289 mShouldInterceptRequestHelper.getParamsForUrl(syncGetUrl).requestHeaders; 290 assertTrue(headers.containsKey(headerName)); 291 assertEquals(headerValue, headers.get(headerName)); 292 } 293 294 @SmallTest 295 @Feature({"AndroidWebView"}) 296 public void testOnLoadResourceCalledWithCorrectUrl() throws Throwable { 297 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 298 final TestAwContentsClient.OnLoadResourceHelper onLoadResourceHelper = 299 mContentsClient.getOnLoadResourceHelper(); 300 301 int callCount = onLoadResourceHelper.getCallCount(); 302 303 loadUrlAsync(mAwContents, aboutPageUrl); 304 305 onLoadResourceHelper.waitForCallback(callCount); 306 assertEquals(aboutPageUrl, onLoadResourceHelper.getUrl()); 307 } 308 309 @SmallTest 310 @Feature({"AndroidWebView"}) 311 public void testDoesNotCrashOnInvalidData() throws Throwable { 312 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 313 314 mShouldInterceptRequestHelper.setReturnValue( 315 new AwWebResourceResponse("text/html", "UTF-8", null)); 316 int callCount = mShouldInterceptRequestHelper.getCallCount(); 317 loadUrlAsync(mAwContents, aboutPageUrl); 318 mShouldInterceptRequestHelper.waitForCallback(callCount); 319 320 mShouldInterceptRequestHelper.setReturnValue( 321 new AwWebResourceResponse(null, null, new ByteArrayInputStream(new byte[0]))); 322 callCount = mShouldInterceptRequestHelper.getCallCount(); 323 loadUrlAsync(mAwContents, aboutPageUrl); 324 mShouldInterceptRequestHelper.waitForCallback(callCount); 325 326 mShouldInterceptRequestHelper.setReturnValue( 327 new AwWebResourceResponse(null, null, null)); 328 callCount = mShouldInterceptRequestHelper.getCallCount(); 329 loadUrlAsync(mAwContents, aboutPageUrl); 330 mShouldInterceptRequestHelper.waitForCallback(callCount); 331 } 332 333 private static class EmptyInputStream extends InputStream { 334 @Override 335 public int available() { 336 return 0; 337 } 338 339 @Override 340 public int read() throws IOException { 341 return -1; 342 } 343 344 @Override 345 public int read(byte b[]) throws IOException { 346 return -1; 347 } 348 349 @Override 350 public int read(byte b[], int off, int len) throws IOException { 351 return -1; 352 } 353 354 @Override 355 public long skip(long n) throws IOException { 356 if (n < 0) 357 throw new IOException("skipping negative number of bytes"); 358 return 0; 359 } 360 } 361 362 @SmallTest 363 @Feature({"AndroidWebView"}) 364 public void testDoesNotCrashOnEmptyStream() throws Throwable { 365 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 366 367 mShouldInterceptRequestHelper.setReturnValue( 368 new AwWebResourceResponse("text/html", "UTF-8", new EmptyInputStream())); 369 int shouldInterceptRequestCallCount = mShouldInterceptRequestHelper.getCallCount(); 370 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); 371 372 loadUrlAsync(mAwContents, aboutPageUrl); 373 374 mShouldInterceptRequestHelper.waitForCallback(shouldInterceptRequestCallCount); 375 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount); 376 } 377 378 private static class ThrowingInputStream extends EmptyInputStream { 379 @Override 380 public int available() { 381 return 100; 382 } 383 384 @Override 385 public int read() throws IOException { 386 throw new IOException("test exception"); 387 } 388 389 @Override 390 public int read(byte b[]) throws IOException { 391 throw new IOException("test exception"); 392 } 393 394 @Override 395 public int read(byte b[], int off, int len) throws IOException { 396 throw new IOException("test exception"); 397 } 398 399 @Override 400 public long skip(long n) throws IOException { 401 return n; 402 } 403 } 404 405 @SmallTest 406 @Feature({"AndroidWebView"}) 407 public void testDoesNotCrashOnThrowingStream() throws Throwable { 408 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 409 410 mShouldInterceptRequestHelper.setReturnValue( 411 new AwWebResourceResponse("text/html", "UTF-8", new ThrowingInputStream())); 412 int shouldInterceptRequestCallCount = mShouldInterceptRequestHelper.getCallCount(); 413 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); 414 415 loadUrlAsync(mAwContents, aboutPageUrl); 416 417 mShouldInterceptRequestHelper.waitForCallback(shouldInterceptRequestCallCount); 418 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount); 419 } 420 421 private static class SlowAwWebResourceResponse extends AwWebResourceResponse { 422 private CallbackHelper mReadStartedCallbackHelper = new CallbackHelper(); 423 private CountDownLatch mLatch = new CountDownLatch(1); 424 425 public SlowAwWebResourceResponse(String mimeType, String encoding, InputStream data) { 426 super(mimeType, encoding, data); 427 } 428 429 @Override 430 public InputStream getData() { 431 mReadStartedCallbackHelper.notifyCalled(); 432 try { 433 mLatch.await(); 434 } catch (InterruptedException e) { 435 // ignore 436 } 437 return super.getData(); 438 } 439 440 public void unblockReads() { 441 mLatch.countDown(); 442 } 443 444 public CallbackHelper getReadStartedCallbackHelper() { 445 return mReadStartedCallbackHelper; 446 } 447 } 448 449 @SmallTest 450 @Feature({"AndroidWebView"}) 451 public void testDoesNotCrashOnSlowStream() throws Throwable { 452 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 453 final String aboutPageData = makePageWithTitle("some title"); 454 final String encoding = "UTF-8"; 455 final SlowAwWebResourceResponse slowAwWebResourceResponse = 456 new SlowAwWebResourceResponse("text/html", encoding, 457 new ByteArrayInputStream(aboutPageData.getBytes(encoding))); 458 459 mShouldInterceptRequestHelper.setReturnValue(slowAwWebResourceResponse); 460 int callCount = slowAwWebResourceResponse.getReadStartedCallbackHelper().getCallCount(); 461 loadUrlAsync(mAwContents, aboutPageUrl); 462 slowAwWebResourceResponse.getReadStartedCallbackHelper().waitForCallback(callCount); 463 464 // Now the AwContents is "stuck" waiting for the SlowInputStream to finish reading so we 465 // delete it to make sure that the dangling 'read' task doesn't cause a crash. Unfortunately 466 // this will not always lead to a crash but it should happen often enough for us to notice. 467 468 runTestOnUiThread(new Runnable() { 469 @Override 470 public void run() { 471 getActivity().removeAllViews(); 472 } 473 }); 474 destroyAwContentsOnMainSync(mAwContents); 475 pollOnUiThread(new Callable<Boolean>() { 476 @Override 477 public Boolean call() { 478 return AwContents.getNativeInstanceCount() == 0; 479 } 480 }); 481 482 slowAwWebResourceResponse.unblockReads(); 483 } 484 485 @SmallTest 486 @Feature({"AndroidWebView"}) 487 public void testHttpStatusCodeAndText() throws Throwable { 488 final String syncGetUrl = mWebServer.getResponseUrl("/intercept_me"); 489 final String syncGetJs = 490 "(function() {" + 491 " var xhr = new XMLHttpRequest();" + 492 " xhr.open('GET', '" + syncGetUrl + "', false);" + 493 " xhr.send(null);" + 494 " console.info('xhr.status = ' + xhr.status);" + 495 " console.info('xhr.statusText = ' + xhr.statusText);" + 496 " return '[' + xhr.status + '][' + xhr.statusText + ']';" + 497 "})();"; 498 enableJavaScriptOnUiThread(mAwContents); 499 500 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 501 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); 502 503 mShouldInterceptRequestHelper.setReturnValue( 504 new AwWebResourceResponse("text/html", "UTF-8", null)); 505 assertEquals("\"[404][Not Found]\"", 506 executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs)); 507 508 mShouldInterceptRequestHelper.setReturnValue( 509 new AwWebResourceResponse("text/html", "UTF-8", new EmptyInputStream())); 510 assertEquals("\"[200][OK]\"", 511 executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs)); 512 513 mShouldInterceptRequestHelper.setReturnValue( 514 new AwWebResourceResponse("text/html", "UTF-8", new EmptyInputStream(), 515 TEAPOT_STATUS_CODE, TEAPOT_RESPONSE_PHRASE, new HashMap<String, String>())); 516 assertEquals("\"[" + TEAPOT_STATUS_CODE + "][" + TEAPOT_RESPONSE_PHRASE + "]\"", 517 executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs)); 518 } 519 520 private String getHeaderValue(AwContents awContents, TestAwContentsClient contentsClient, 521 String url, String headerName) throws Exception { 522 final String syncGetJs = 523 "(function() {" + 524 " var xhr = new XMLHttpRequest();" + 525 " xhr.open('GET', '" + url + "', false);" + 526 " xhr.send(null);" + 527 " console.info(xhr.getAllResponseHeaders());" + 528 " return xhr.getResponseHeader('" + headerName + "');" + 529 "})();"; 530 String header = executeJavaScriptAndWaitForResult(awContents, contentsClient, syncGetJs); 531 532 if (header.equals("null")) 533 return null; 534 // JSON stringification applied by executeJavaScriptAndWaitForResult adds quotes 535 // around returned strings. 536 assertTrue(header.length() > 2); 537 assertEquals('"', header.charAt(0)); 538 assertEquals('"', header.charAt(header.length() - 1)); 539 return header.substring(1, header.length() - 1); 540 } 541 542 @SmallTest 543 @Feature({"AndroidWebView"}) 544 public void testHttpResponseClientViaHeader() throws Throwable { 545 final String clientResponseHeaderName = "Client-Via"; 546 final String clientResponseHeaderValue = "shouldInterceptRequest"; 547 final String syncGetUrl = mWebServer.getResponseUrl("/intercept_me"); 548 enableJavaScriptOnUiThread(mAwContents); 549 550 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 551 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); 552 553 // The response header is set regardless of whether the embedder has provided a 554 // valid resource stream. 555 mShouldInterceptRequestHelper.setReturnValue( 556 new AwWebResourceResponse("text/html", "UTF-8", null)); 557 assertEquals(clientResponseHeaderValue, 558 getHeaderValue(mAwContents, mContentsClient, syncGetUrl, clientResponseHeaderName)); 559 mShouldInterceptRequestHelper.setReturnValue( 560 new AwWebResourceResponse("text/html", "UTF-8", new EmptyInputStream())); 561 assertEquals(clientResponseHeaderValue, 562 getHeaderValue(mAwContents, mContentsClient, syncGetUrl, clientResponseHeaderName)); 563 564 } 565 566 @SmallTest 567 @Feature({"AndroidWebView"}) 568 public void testHttpResponseHeader() throws Throwable { 569 final String clientResponseHeaderName = "X-Test-Header-Name"; 570 final String clientResponseHeaderValue = "TestHeaderValue"; 571 final String syncGetUrl = mWebServer.getResponseUrl("/intercept_me"); 572 final Map<String, String> headers = new HashMap<String, String>(); 573 headers.put(clientResponseHeaderName, clientResponseHeaderValue); 574 enableJavaScriptOnUiThread(mAwContents); 575 576 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 577 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); 578 579 mShouldInterceptRequestHelper.setReturnValue( 580 new AwWebResourceResponse("text/html", "UTF-8", null, 0, null, headers)); 581 assertEquals(clientResponseHeaderValue, 582 getHeaderValue(mAwContents, mContentsClient, syncGetUrl, clientResponseHeaderName)); 583 } 584 585 @SmallTest 586 @Feature({"AndroidWebView"}) 587 public void testNullHttpResponseHeaders() throws Throwable { 588 final String syncGetUrl = mWebServer.getResponseUrl("/intercept_me"); 589 enableJavaScriptOnUiThread(mAwContents); 590 591 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 592 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); 593 594 mShouldInterceptRequestHelper.setReturnValue( 595 new AwWebResourceResponse("text/html", "UTF-8", null, 0, null, null)); 596 assertEquals(null, getHeaderValue(mAwContents, mContentsClient, syncGetUrl, "Some-Header")); 597 } 598 599 private String makePageWithTitle(String title) { 600 return CommonResources.makeHtmlPageFrom("<title>" + title + "</title>", 601 "<div> The title is: " + title + " </div>"); 602 } 603 604 @SmallTest 605 @Feature({"AndroidWebView"}) 606 public void testCanInterceptMainFrame() throws Throwable { 607 final String expectedTitle = "testShouldInterceptRequestCanInterceptMainFrame"; 608 final String expectedPage = makePageWithTitle(expectedTitle); 609 610 mShouldInterceptRequestHelper.setReturnValue( 611 stringToAwWebResourceResponse(expectedPage)); 612 613 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 614 615 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); 616 617 assertEquals(expectedTitle, getTitleOnUiThread(mAwContents)); 618 assertEquals(0, mWebServer.getRequestCount("/" + CommonResources.ABOUT_FILENAME)); 619 } 620 621 @SmallTest 622 @Feature({"AndroidWebView"}) 623 public void testDoesNotChangeReportedUrl() throws Throwable { 624 mShouldInterceptRequestHelper.setReturnValue( 625 stringToAwWebResourceResponse(makePageWithTitle("some title"))); 626 627 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 628 629 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); 630 631 assertEquals(aboutPageUrl, mContentsClient.getOnPageFinishedHelper().getUrl()); 632 assertEquals(aboutPageUrl, mContentsClient.getOnPageStartedHelper().getUrl()); 633 } 634 635 @SmallTest 636 @Feature({"AndroidWebView"}) 637 public void testNullInputStreamCausesErrorForMainFrame() throws Throwable { 638 final OnReceivedErrorHelper onReceivedErrorHelper = 639 mContentsClient.getOnReceivedErrorHelper(); 640 641 mShouldInterceptRequestHelper.setReturnValue( 642 new AwWebResourceResponse("text/html", "UTF-8", null)); 643 644 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 645 final int callCount = onReceivedErrorHelper.getCallCount(); 646 loadUrlAsync(mAwContents, aboutPageUrl); 647 onReceivedErrorHelper.waitForCallback(callCount); 648 assertEquals(0, mWebServer.getRequestCount("/" + CommonResources.ABOUT_FILENAME)); 649 } 650 651 @SmallTest 652 @Feature({"AndroidWebView"}) 653 public void testCalledForImage() throws Throwable { 654 final String imagePath = "/" + CommonResources.FAVICON_FILENAME; 655 mWebServer.setResponseBase64(imagePath, 656 CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true)); 657 final String pageWithImage = 658 addPageToTestServer(mWebServer, "/page_with_image.html", 659 CommonResources.getOnImageLoadedHtml(CommonResources.FAVICON_FILENAME)); 660 661 int callCount = mShouldInterceptRequestHelper.getCallCount(); 662 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithImage); 663 mShouldInterceptRequestHelper.waitForCallback(callCount, 2); 664 665 assertEquals(2, mShouldInterceptRequestHelper.getUrls().size()); 666 assertTrue(mShouldInterceptRequestHelper.getUrls().get(1).endsWith( 667 CommonResources.FAVICON_FILENAME)); 668 } 669 670 @SmallTest 671 @Feature({"AndroidWebView"}) 672 public void testOnReceivedErrorCallback() throws Throwable { 673 mShouldInterceptRequestHelper.setReturnValue(new AwWebResourceResponse(null, null, null)); 674 OnReceivedErrorHelper onReceivedErrorHelper = mContentsClient.getOnReceivedErrorHelper(); 675 int onReceivedErrorHelperCallCount = onReceivedErrorHelper.getCallCount(); 676 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), "foo://bar"); 677 onReceivedErrorHelper.waitForCallback(onReceivedErrorHelperCallCount, 1); 678 } 679 680 @SmallTest 681 @Feature({"AndroidWebView"}) 682 public void testNoOnReceivedErrorCallback() throws Throwable { 683 final String imagePath = "/" + CommonResources.FAVICON_FILENAME; 684 final String imageUrl = mWebServer.setResponseBase64(imagePath, 685 CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true)); 686 final String pageWithImage = 687 addPageToTestServer(mWebServer, "/page_with_image.html", 688 CommonResources.getOnImageLoadedHtml(CommonResources.FAVICON_FILENAME)); 689 mShouldInterceptRequestHelper.setReturnValueForUrl( 690 imageUrl, new AwWebResourceResponse(null, null, null)); 691 OnReceivedErrorHelper onReceivedErrorHelper = mContentsClient.getOnReceivedErrorHelper(); 692 int onReceivedErrorHelperCallCount = onReceivedErrorHelper.getCallCount(); 693 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithImage); 694 assertEquals(onReceivedErrorHelperCallCount, onReceivedErrorHelper.getCallCount()); 695 } 696 697 @SmallTest 698 @Feature({"AndroidWebView"}) 699 public void testCalledForIframe() throws Throwable { 700 final String aboutPageUrl = addAboutPageToTestServer(mWebServer); 701 final String pageWithIframeUrl = addPageToTestServer(mWebServer, "/page_with_iframe.html", 702 CommonResources.makeHtmlPageFrom("", 703 "<iframe src=\"" + aboutPageUrl + "\"/>")); 704 705 int callCount = mShouldInterceptRequestHelper.getCallCount(); 706 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithIframeUrl); 707 mShouldInterceptRequestHelper.waitForCallback(callCount, 2); 708 assertEquals(2, mShouldInterceptRequestHelper.getUrls().size()); 709 assertEquals(aboutPageUrl, mShouldInterceptRequestHelper.getUrls().get(1)); 710 } 711 712 private void calledForUrlTemplate(final String url) throws Exception { 713 int callCount = mShouldInterceptRequestHelper.getCallCount(); 714 int onPageStartedCallCount = mContentsClient.getOnPageStartedHelper().getCallCount(); 715 loadUrlAsync(mAwContents, url); 716 mShouldInterceptRequestHelper.waitForCallback(callCount); 717 assertEquals(url, mShouldInterceptRequestHelper.getUrls().get(0)); 718 719 mContentsClient.getOnPageStartedHelper().waitForCallback(onPageStartedCallCount); 720 assertEquals(onPageStartedCallCount + 1, 721 mContentsClient.getOnPageStartedHelper().getCallCount()); 722 } 723 724 private void notCalledForUrlTemplate(final String url) throws Exception { 725 int callCount = mShouldInterceptRequestHelper.getCallCount(); 726 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url); 727 // The intercepting must happen before onPageFinished. Since the IPC messages from the 728 // renderer should be delivered in order waiting for onPageFinished is sufficient to 729 // 'flush' any pending interception messages. 730 assertEquals(callCount, mShouldInterceptRequestHelper.getCallCount()); 731 } 732 733 @SmallTest 734 @Feature({"AndroidWebView"}) 735 public void testCalledForUnsupportedSchemes() throws Throwable { 736 calledForUrlTemplate("foobar://resource/1"); 737 } 738 739 @SmallTest 740 @Feature({"AndroidWebView"}) 741 public void testCalledForNonexistentFiles() throws Throwable { 742 calledForUrlTemplate("file:///somewhere/something"); 743 } 744 745 @SmallTest 746 @Feature({"AndroidWebView"}) 747 public void testCalledForExistingFiles() throws Throwable { 748 final String tmpDir = getInstrumentation().getTargetContext().getCacheDir().getPath(); 749 final String fileName = tmpDir + "/testfile.html"; 750 final String title = "existing file title"; 751 TestFileUtil.deleteFile(fileName); // Remove leftover file if any. 752 TestFileUtil.createNewHtmlFile(fileName, title, ""); 753 final String existingFileUrl = "file://" + fileName; 754 755 int callCount = mShouldInterceptRequestHelper.getCallCount(); 756 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount(); 757 loadUrlAsync(mAwContents, existingFileUrl); 758 mShouldInterceptRequestHelper.waitForCallback(callCount); 759 assertEquals(existingFileUrl, mShouldInterceptRequestHelper.getUrls().get(0)); 760 761 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount); 762 assertEquals(title, getTitleOnUiThread(mAwContents)); 763 assertEquals(onPageFinishedCallCount + 1, 764 mContentsClient.getOnPageFinishedHelper().getCallCount()); 765 } 766 767 @SmallTest 768 @Feature({"AndroidWebView"}) 769 public void testNotCalledForExistingResource() throws Throwable { 770 notCalledForUrlTemplate("file:///android_res/raw/resource_file.html"); 771 } 772 773 @SmallTest 774 @Feature({"AndroidWebView"}) 775 public void testCalledForNonexistentResource() throws Throwable { 776 calledForUrlTemplate("file:///android_res/raw/no_file.html"); 777 } 778 779 @SmallTest 780 @Feature({"AndroidWebView"}) 781 public void testNotCalledForExistingAsset() throws Throwable { 782 notCalledForUrlTemplate("file:///android_asset/asset_file.html"); 783 } 784 785 @SmallTest 786 @Feature({"AndroidWebView"}) 787 public void testCalledForNonexistentAsset() throws Throwable { 788 calledForUrlTemplate("file:///android_res/raw/no_file.html"); 789 } 790 791 @SmallTest 792 @Feature({"AndroidWebView"}) 793 public void testNotCalledForExistingContentUrl() throws Throwable { 794 final String contentResourceName = "target"; 795 final String existingContentUrl = TestContentProvider.createContentUrl(contentResourceName); 796 797 notCalledForUrlTemplate(existingContentUrl); 798 799 int contentRequestCount = TestContentProvider.getResourceRequestCount( 800 getInstrumentation().getTargetContext(), contentResourceName); 801 assertEquals(1, contentRequestCount); 802 } 803 804 @SmallTest 805 @Feature({"AndroidWebView"}) 806 public void testCalledForNonexistentContentUrl() throws Throwable { 807 calledForUrlTemplate("content://org.chromium.webview.NoSuchProvider/foo"); 808 } 809} 810