1 // Copyright (c) 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.
5 package org.chromium.android_webview.test;
7 import android.test.suitebuilder.annotation.SmallTest;
8 import android.util.Log;
9 import android.util.Pair;
11 import org.chromium.android_webview.AndroidProtocolHandler;
12 import org.chromium.android_webview.AwContents;
13 import org.chromium.android_webview.InterceptedRequestData;
14 import org.chromium.android_webview.test.util.CommonResources;
15 import org.chromium.android_webview.test.util.JSUtils;
16 import org.chromium.base.test.util.Feature;
17 import org.chromium.base.test.util.TestFileUtil;
18 import org.chromium.content.browser.test.util.CallbackHelper;
19 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnPageFinishedHelper;
20 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnPageStartedHelper;
21 import org.chromium.content.browser.test.util.TestCallbackHelperContainer.OnReceivedErrorHelper;
22 import org.chromium.net.test.util.TestWebServer;
24 import java.io.ByteArrayInputStream;
25 import java.io.InputStream;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.concurrent.ConcurrentHashMap;
29 import java.util.List;
30 import java.util.Random;
33 * Tests for the WebViewClient.shouldInterceptRequest() method.
35 public class AwContentsClientShouldInterceptRequestTest extends AwTestBase {
37 private static class TestAwContentsClient
38 extends org.chromium.android_webview.test.TestAwContentsClient {
40 public static class ShouldInterceptRequestHelper extends CallbackHelper {
41 private List<String> mShouldInterceptRequestUrls = new ArrayList<String>();
42 private ConcurrentHashMap<String, InterceptedRequestData> mReturnValuesByUrls
43 = new ConcurrentHashMap<String, InterceptedRequestData>();
44 // This is read from the IO thread, so needs to be marked volatile.
45 private volatile InterceptedRequestData mShouldInterceptRequestReturnValue = null;
46 void setReturnValue(InterceptedRequestData value) {
47 mShouldInterceptRequestReturnValue = value;
49 void setReturnValueForUrl(String url, InterceptedRequestData value) {
50 mReturnValuesByUrls.put(url, value);
52 public List<String> getUrls() {
53 assert getCallCount() > 0;
54 return mShouldInterceptRequestUrls;
56 public InterceptedRequestData getReturnValue(String url) {
57 InterceptedRequestData value = mReturnValuesByUrls.get(url);
58 if (value != null) return value;
59 return mShouldInterceptRequestReturnValue;
61 public void notifyCalled(String url) {
62 mShouldInterceptRequestUrls.add(url);
67 public static class OnLoadResourceHelper extends CallbackHelper {
70 public String getUrl() {
71 assert getCallCount() > 0;
75 public void notifyCalled(String url) {
82 public InterceptedRequestData shouldInterceptRequest(String url) {
83 InterceptedRequestData returnValue = mShouldInterceptRequestHelper.getReturnValue(url);
84 mShouldInterceptRequestHelper.notifyCalled(url);
89 public void onLoadResource(String url) {
90 super.onLoadResource(url);
91 mOnLoadResourceHelper.notifyCalled(url);
94 private ShouldInterceptRequestHelper mShouldInterceptRequestHelper;
95 private OnLoadResourceHelper mOnLoadResourceHelper;
97 public TestAwContentsClient() {
98 mShouldInterceptRequestHelper = new ShouldInterceptRequestHelper();
99 mOnLoadResourceHelper = new OnLoadResourceHelper();
102 public ShouldInterceptRequestHelper getShouldInterceptRequestHelper() {
103 return mShouldInterceptRequestHelper;
106 public OnLoadResourceHelper getOnLoadResourceHelper() {
107 return mOnLoadResourceHelper;
111 private String addPageToTestServer(TestWebServer webServer, String httpPath, String html) {
112 List<Pair<String, String>> headers = new ArrayList<Pair<String, String>>();
113 headers.add(Pair.create("Content-Type", "text/html"));
114 headers.add(Pair.create("Cache-Control", "no-store"));
115 return webServer.setResponse(httpPath, html, headers);
118 private String addAboutPageToTestServer(TestWebServer webServer) {
119 return addPageToTestServer(webServer, "/" + CommonResources.ABOUT_FILENAME,
120 CommonResources.ABOUT_HTML);
123 private InterceptedRequestData stringToInterceptedRequestData(String input) throws Throwable {
124 final String mimeType = "text/html";
125 final String encoding = "UTF-8";
127 return new InterceptedRequestData(
128 mimeType, encoding, new ByteArrayInputStream(input.getBytes(encoding)));
131 private TestWebServer mWebServer;
132 private TestAwContentsClient mContentsClient;
133 private AwTestContainerView mTestContainerView;
134 private AwContents mAwContents;
135 private TestAwContentsClient.ShouldInterceptRequestHelper mShouldInterceptRequestHelper;
138 protected void setUp() throws Exception {
141 mContentsClient = new TestAwContentsClient();
142 mTestContainerView = createAwTestContainerViewOnMainSync(mContentsClient);
143 mAwContents = mTestContainerView.getAwContents();
144 mShouldInterceptRequestHelper = mContentsClient.getShouldInterceptRequestHelper();
146 mWebServer = new TestWebServer(false);
150 protected void tearDown() throws Exception {
151 mWebServer.shutdown();
156 @Feature({"AndroidWebView"})
157 public void testCalledWithCorrectUrl() throws Throwable {
158 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
160 int callCount = mShouldInterceptRequestHelper.getCallCount();
161 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
163 loadUrlAsync(mAwContents, aboutPageUrl);
165 mShouldInterceptRequestHelper.waitForCallback(callCount);
166 assertEquals(1, mShouldInterceptRequestHelper.getUrls().size());
167 assertEquals(aboutPageUrl,
168 mShouldInterceptRequestHelper.getUrls().get(0));
170 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount);
171 assertEquals(CommonResources.ABOUT_TITLE, getTitleOnUiThread(mAwContents));
175 @Feature({"AndroidWebView"})
176 public void testOnLoadResourceCalledWithCorrectUrl() throws Throwable {
177 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
178 final TestAwContentsClient.OnLoadResourceHelper onLoadResourceHelper =
179 mContentsClient.getOnLoadResourceHelper();
181 int callCount = onLoadResourceHelper.getCallCount();
183 loadUrlAsync(mAwContents, aboutPageUrl);
185 onLoadResourceHelper.waitForCallback(callCount);
186 assertEquals(aboutPageUrl, onLoadResourceHelper.getUrl());
190 @Feature({"AndroidWebView"})
191 public void testDoesNotCrashOnInvalidData() throws Throwable {
192 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
194 mShouldInterceptRequestHelper.setReturnValue(
195 new InterceptedRequestData("text/html", "UTF-8", null));
196 int callCount = mShouldInterceptRequestHelper.getCallCount();
197 loadUrlAsync(mAwContents, aboutPageUrl);
198 mShouldInterceptRequestHelper.waitForCallback(callCount);
200 mShouldInterceptRequestHelper.setReturnValue(
201 new InterceptedRequestData(null, null, new ByteArrayInputStream(new byte[0])));
202 callCount = mShouldInterceptRequestHelper.getCallCount();
203 loadUrlAsync(mAwContents, aboutPageUrl);
204 mShouldInterceptRequestHelper.waitForCallback(callCount);
206 mShouldInterceptRequestHelper.setReturnValue(
207 new InterceptedRequestData(null, null, null));
208 callCount = mShouldInterceptRequestHelper.getCallCount();
209 loadUrlAsync(mAwContents, aboutPageUrl);
210 mShouldInterceptRequestHelper.waitForCallback(callCount);
213 private static class EmptyInputStream extends InputStream {
215 public int available() {
220 public int read() throws IOException {
225 public int read(byte b[]) throws IOException {
230 public int read(byte b[], int off, int len) throws IOException {
235 public long skip(long n) throws IOException {
237 throw new IOException("skipping negative number of bytes");
243 @Feature({"AndroidWebView"})
244 public void testDoesNotCrashOnEmptyStream() throws Throwable {
245 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
247 mShouldInterceptRequestHelper.setReturnValue(
248 new InterceptedRequestData("text/html", "UTF-8", new EmptyInputStream()));
249 int shouldInterceptRequestCallCount = mShouldInterceptRequestHelper.getCallCount();
250 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
252 loadUrlAsync(mAwContents, aboutPageUrl);
254 mShouldInterceptRequestHelper.waitForCallback(shouldInterceptRequestCallCount);
255 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount);
259 @Feature({"AndroidWebView"})
260 public void testHttpStatusField() throws Throwable {
261 final String syncGetUrl = mWebServer.getResponseUrl("/intercept_me");
262 final String syncGetJs =
264 " var xhr = new XMLHttpRequest();" +
265 " xhr.open('GET', '" + syncGetUrl + "', false);" +
267 " console.info('xhr.status = ' + xhr.status);" +
268 " return xhr.status;" +
270 enableJavaScriptOnUiThread(mAwContents);
272 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
273 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl);
275 mShouldInterceptRequestHelper.setReturnValue(
276 new InterceptedRequestData("text/html", "UTF-8", null));
278 executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs));
280 mShouldInterceptRequestHelper.setReturnValue(
281 new InterceptedRequestData("text/html", "UTF-8", new EmptyInputStream()));
283 executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs));
287 private String makePageWithTitle(String title) {
288 return CommonResources.makeHtmlPageFrom("<title>" + title + "</title>",
289 "<div> The title is: " + title + " </div>");
293 @Feature({"AndroidWebView"})
294 public void testCanInterceptMainFrame() throws Throwable {
295 final String expectedTitle = "testShouldInterceptRequestCanInterceptMainFrame";
296 final String expectedPage = makePageWithTitle(expectedTitle);
298 mShouldInterceptRequestHelper.setReturnValue(
299 stringToInterceptedRequestData(expectedPage));
301 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
303 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl);
305 assertEquals(expectedTitle, getTitleOnUiThread(mAwContents));
306 assertEquals(0, mWebServer.getRequestCount("/" + CommonResources.ABOUT_FILENAME));
310 @Feature({"AndroidWebView"})
311 public void testDoesNotChangeReportedUrl() throws Throwable {
312 mShouldInterceptRequestHelper.setReturnValue(
313 stringToInterceptedRequestData(makePageWithTitle("some title")));
315 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
317 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl);
319 assertEquals(aboutPageUrl, mContentsClient.getOnPageFinishedHelper().getUrl());
320 assertEquals(aboutPageUrl, mContentsClient.getOnPageStartedHelper().getUrl());
324 @Feature({"AndroidWebView"})
325 public void testNullInputStreamCausesErrorForMainFrame() throws Throwable {
326 final OnReceivedErrorHelper onReceivedErrorHelper =
327 mContentsClient.getOnReceivedErrorHelper();
329 mShouldInterceptRequestHelper.setReturnValue(
330 new InterceptedRequestData("text/html", "UTF-8", null));
332 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
333 final int callCount = onReceivedErrorHelper.getCallCount();
334 loadUrlAsync(mAwContents, aboutPageUrl);
335 onReceivedErrorHelper.waitForCallback(callCount);
336 assertEquals(0, mWebServer.getRequestCount("/" + CommonResources.ABOUT_FILENAME));
340 @Feature({"AndroidWebView"})
341 public void testCalledForImage() throws Throwable {
342 final String imagePath = "/" + CommonResources.FAVICON_FILENAME;
343 mWebServer.setResponseBase64(imagePath,
344 CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true));
345 final String pageWithImage =
346 addPageToTestServer(mWebServer, "/page_with_image.html",
347 CommonResources.getOnImageLoadedHtml(CommonResources.FAVICON_FILENAME));
349 int callCount = mShouldInterceptRequestHelper.getCallCount();
350 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithImage);
351 mShouldInterceptRequestHelper.waitForCallback(callCount, 2);
353 assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
354 assertTrue(mShouldInterceptRequestHelper.getUrls().get(1).endsWith(
355 CommonResources.FAVICON_FILENAME));
359 @Feature({"AndroidWebView"})
360 public void testOnReceivedErrorCallback() throws Throwable {
361 mShouldInterceptRequestHelper.setReturnValue(new InterceptedRequestData(null, null, null));
362 OnReceivedErrorHelper onReceivedErrorHelper = mContentsClient.getOnReceivedErrorHelper();
363 int onReceivedErrorHelperCallCount = onReceivedErrorHelper.getCallCount();
364 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), "foo://bar");
365 onReceivedErrorHelper.waitForCallback(onReceivedErrorHelperCallCount, 1);
369 @Feature({"AndroidWebView"})
370 public void testNoOnReceivedErrorCallback() throws Throwable {
371 final String imagePath = "/" + CommonResources.FAVICON_FILENAME;
372 final String imageUrl = mWebServer.setResponseBase64(imagePath,
373 CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true));
374 final String pageWithImage =
375 addPageToTestServer(mWebServer, "/page_with_image.html",
376 CommonResources.getOnImageLoadedHtml(CommonResources.FAVICON_FILENAME));
377 mShouldInterceptRequestHelper.setReturnValueForUrl(
378 imageUrl, new InterceptedRequestData(null, null, null));
379 OnReceivedErrorHelper onReceivedErrorHelper = mContentsClient.getOnReceivedErrorHelper();
380 int onReceivedErrorHelperCallCount = onReceivedErrorHelper.getCallCount();
381 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithImage);
382 assertEquals(onReceivedErrorHelperCallCount, onReceivedErrorHelper.getCallCount());
386 @Feature({"AndroidWebView"})
387 public void testCalledForIframe() throws Throwable {
388 final String aboutPageUrl = addAboutPageToTestServer(mWebServer);
389 final String pageWithIframe = addPageToTestServer(mWebServer, "/page_with_iframe.html",
390 CommonResources.makeHtmlPageFrom("",
391 "<iframe src=\"" + aboutPageUrl + "\"/>"));
393 int callCount = mShouldInterceptRequestHelper.getCallCount();
394 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageWithIframe);
395 mShouldInterceptRequestHelper.waitForCallback(callCount, 2);
396 assertEquals(2, mShouldInterceptRequestHelper.getUrls().size());
397 assertEquals(aboutPageUrl, mShouldInterceptRequestHelper.getUrls().get(1));
400 private void calledForUrlTemplate(final String url) throws Exception {
401 int callCount = mShouldInterceptRequestHelper.getCallCount();
402 int onPageStartedCallCount = mContentsClient.getOnPageStartedHelper().getCallCount();
403 loadUrlAsync(mAwContents, url);
404 mShouldInterceptRequestHelper.waitForCallback(callCount);
405 assertEquals(url, mShouldInterceptRequestHelper.getUrls().get(0));
407 mContentsClient.getOnPageStartedHelper().waitForCallback(onPageStartedCallCount);
408 assertEquals(onPageStartedCallCount + 1,
409 mContentsClient.getOnPageStartedHelper().getCallCount());
412 private void notCalledForUrlTemplate(final String url) throws Exception {
413 int callCount = mShouldInterceptRequestHelper.getCallCount();
414 loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
415 // The intercepting must happen before onPageFinished. Since the IPC messages from the
416 // renderer should be delivered in order waiting for onPageFinished is sufficient to
417 // 'flush' any pending interception messages.
418 assertEquals(callCount, mShouldInterceptRequestHelper.getCallCount());
422 @Feature({"AndroidWebView"})
423 public void testCalledForUnsupportedSchemes() throws Throwable {
424 calledForUrlTemplate("foobar://resource/1");
428 @Feature({"AndroidWebView"})
429 public void testCalledForNonexistentFiles() throws Throwable {
430 calledForUrlTemplate("file:///somewhere/something");
434 @Feature({"AndroidWebView"})
435 public void testCalledForExistingFiles() throws Throwable {
436 final String tmpDir = getInstrumentation().getTargetContext().getCacheDir().getPath();
437 final String fileName = tmpDir + "/testfile.html";
438 final String title = "existing file title";
439 TestFileUtil.deleteFile(fileName); // Remove leftover file if any.
440 TestFileUtil.createNewHtmlFile(fileName, title, "");
441 final String existingFileUrl = "file://" + fileName;
443 int callCount = mShouldInterceptRequestHelper.getCallCount();
444 int onPageFinishedCallCount = mContentsClient.getOnPageFinishedHelper().getCallCount();
445 loadUrlAsync(mAwContents, existingFileUrl);
446 mShouldInterceptRequestHelper.waitForCallback(callCount);
447 assertEquals(existingFileUrl, mShouldInterceptRequestHelper.getUrls().get(0));
449 mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount);
450 assertEquals(title, getTitleOnUiThread(mAwContents));
451 assertEquals(onPageFinishedCallCount + 1,
452 mContentsClient.getOnPageFinishedHelper().getCallCount());
456 @Feature({"AndroidWebView"})
457 public void testNotCalledForExistingResource() throws Throwable {
458 notCalledForUrlTemplate("file:///android_res/raw/resource_file.html");
462 @Feature({"AndroidWebView"})
463 public void testCalledForNonexistentResource() throws Throwable {
464 calledForUrlTemplate("file:///android_res/raw/no_file.html");
468 @Feature({"AndroidWebView"})
469 public void testNotCalledForExistingAsset() throws Throwable {
470 notCalledForUrlTemplate("file:///android_asset/asset_file.html");
474 @Feature({"AndroidWebView"})
475 public void testCalledForNonexistentAsset() throws Throwable {
476 calledForUrlTemplate("file:///android_res/raw/no_file.html");
480 @Feature({"AndroidWebView"})
481 public void testNotCalledForExistingContentUrl() throws Throwable {
482 final String contentResourceName = "target";
483 final String existingContentUrl = TestContentProvider.createContentUrl(contentResourceName);
485 notCalledForUrlTemplate(existingContentUrl);
487 int contentRequestCount = TestContentProvider.getResourceRequestCount(
488 getInstrumentation().getTargetContext(), contentResourceName);
489 assertEquals(1, contentRequestCount);
493 @Feature({"AndroidWebView"})
494 public void testCalledForNonexistentContentUrl() throws Throwable {
495 calledForUrlTemplate("content://org.chromium.webview.NoSuchProvider/foo");