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