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.BitmapFactory;
9import android.test.suitebuilder.annotation.SmallTest;
10
11import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;
12
13import org.chromium.android_webview.AwContents;
14import org.chromium.android_webview.test.util.CommonResources;
15import org.chromium.content.browser.test.util.CallbackHelper;
16import org.chromium.net.test.util.TestWebServer;
17
18import java.io.InputStream;
19import java.net.URL;
20import java.util.HashMap;
21import java.util.concurrent.Callable;
22
23/**
24 * Tests for the Favicon and TouchIcon related APIs.
25 */
26public class AwContentsClientFaviconTest extends AwTestBase {
27
28    private static final String FAVICON1_URL = "/favicon1.png";
29    private static final String FAVICON1_PAGE_URL = "/favicon1.html";
30    private static final String FAVICON1_PAGE_HTML =
31            CommonResources.makeHtmlPageFrom(
32                    "<link rel=\"icon\" href=\"" + FAVICON1_URL + "\" />",
33                    "Body");
34
35    private static final String TOUCHICON_REL_LINK = "touch.png";
36    private static final String TOUCHICON_REL_LINK_72 = "touch_72.png";
37    private static final String TOUCHICON_REL_URL = "/" + TOUCHICON_REL_LINK;
38    private static final String TOUCHICON_REL_URL_72 = "/" + TOUCHICON_REL_LINK_72;
39    private static final String TOUCHICON_REL_PAGE_HTML =
40            CommonResources.makeHtmlPageFrom(
41                    "<link rel=\"apple-touch-icon\" href=\"" + TOUCHICON_REL_URL + "\" />" +
42                    "<link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"" + TOUCHICON_REL_URL_72
43                    + "\" />",
44                    "Body");
45
46    // Maximum number of milliseconds within which a request to web server is made.
47    private static final long MAX_REQUEST_WAITING_LIMIT_MS = scaleTimeout(500);
48
49    private static class FaviconHelper extends CallbackHelper {
50        private Bitmap mIcon;
51        private HashMap<String, Boolean> mTouchIcons = new HashMap<String, Boolean>();
52
53        public void notifyFavicon(Bitmap icon) {
54            mIcon = icon;
55            super.notifyCalled();
56        }
57
58        public void notifyTouchIcon(String url, boolean precomposed) {
59            mTouchIcons.put(url, precomposed);
60            super.notifyCalled();
61        }
62    }
63
64    private static class TestAwContentsClientBase
65            extends org.chromium.android_webview.test.TestAwContentsClient {
66        FaviconHelper mFaviconHelper = new FaviconHelper();
67    }
68
69    private static class TestAwContentsClientFavicon extends TestAwContentsClientBase {
70        @Override
71        public void onReceivedIcon(Bitmap bitmap) {
72            // We don't inform the API client about the URL of the icon.
73            mFaviconHelper.notifyFavicon(bitmap);
74        }
75    }
76
77    private static class TestAwContentsClientTouchIcon extends TestAwContentsClientBase {
78        @Override
79        public void onReceivedTouchIconUrl(String url, boolean precomposed) {
80            mFaviconHelper.notifyTouchIcon(url, precomposed);
81        }
82    }
83
84    private TestAwContentsClientBase mContentsClient;
85    private AwContents mAwContents;
86    private TestWebServer mWebServer;
87
88    @Override
89    protected void setUp() throws Exception {
90        super.setUp();
91        AwContents.setShouldDownloadFavicons();
92        mWebServer = new TestWebServer(false);
93    }
94
95    private void init(TestAwContentsClientBase contentsClient) throws Exception {
96        mContentsClient = contentsClient;
97        AwTestContainerView testContainerView =
98                createAwTestContainerViewOnMainSync(mContentsClient);
99        mAwContents = testContainerView.getAwContents();
100    }
101
102    @Override
103    protected void tearDown() throws Exception {
104        if (mWebServer != null) mWebServer.shutdown();
105        super.tearDown();
106    }
107
108    @SmallTest
109    public void testReceiveBasicFavicon() throws Throwable {
110        init(new TestAwContentsClientFavicon());
111        int callCount = mContentsClient.mFaviconHelper.getCallCount();
112
113        final String faviconUrl = mWebServer.setResponseBase64(FAVICON1_URL,
114            CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true));
115        final String pageUrl = mWebServer.setResponse(FAVICON1_PAGE_URL, FAVICON1_PAGE_HTML,
116            CommonResources.getTextHtmlHeaders(true));
117
118        loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
119
120        mContentsClient.mFaviconHelper.waitForCallback(callCount);
121        assertEquals(1, mWebServer.getRequestCount(FAVICON1_URL));
122        Object originalFaviconSource = (new URL(faviconUrl)).getContent();
123        Bitmap originalFavicon = BitmapFactory.decodeStream((InputStream) originalFaviconSource);
124        assertNotNull(originalFavicon);
125        assertNotNull(mContentsClient.mFaviconHelper.mIcon);
126        assertTrue(mContentsClient.mFaviconHelper.mIcon.sameAs(originalFavicon));
127
128        // Make sure the request counter for favicon is incremented when the page is loaded again
129        // successfully.
130        loadUrlAsync(mAwContents, pageUrl);
131        mContentsClient.mFaviconHelper.waitForCallback(callCount);
132        assertEquals(2, mWebServer.getRequestCount(FAVICON1_URL));
133    }
134
135    @SmallTest
136    public void testDoNotMakeRequestForFaviconAfter404() throws Throwable {
137        init(new TestAwContentsClientFavicon());
138
139        mWebServer.setResponseWithNotFoundStatus(FAVICON1_URL);
140        final String pageUrl = mWebServer.setResponse(FAVICON1_PAGE_URL, FAVICON1_PAGE_HTML,
141            CommonResources.getTextHtmlHeaders(true));
142
143        loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
144        poll(new Callable<Boolean>() {
145            @Override
146            public Boolean call() throws Exception {
147                return mWebServer.getRequestCount(FAVICON1_URL) == 1;
148            }
149        });
150
151        // Make sure the request counter for favicon is not incremented, since we already got 404.
152        loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
153        // If a request hasn't been done within this time period, we assume it won't be done.
154        Thread.sleep(MAX_REQUEST_WAITING_LIMIT_MS);
155        assertEquals(1, mWebServer.getRequestCount(FAVICON1_URL));
156    }
157
158    @SmallTest
159    public void testReceiveBasicTouchIconLinkRel() throws Throwable {
160        init(new TestAwContentsClientTouchIcon());
161        int callCount = mContentsClient.mFaviconHelper.getCallCount();
162
163        final String pageUrl = mWebServer.setResponse(TOUCHICON_REL_URL, TOUCHICON_REL_PAGE_HTML,
164            CommonResources.getTextHtmlHeaders(true));
165
166        loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
167
168        mContentsClient.mFaviconHelper.waitForCallback(callCount, 2);
169        HashMap<String, Boolean> touchIcons = mContentsClient.mFaviconHelper.mTouchIcons;
170        assertEquals(2, touchIcons.size());
171        assertFalse(touchIcons.get(mWebServer.getBaseUrl() + TOUCHICON_REL_LINK));
172        assertFalse(touchIcons.get(mWebServer.getBaseUrl() + TOUCHICON_REL_LINK_72));
173    }
174}
175