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.
4
5package org.chromium.android_webview.test;
6
7import android.test.FlakyTest;
8import android.test.suitebuilder.annotation.MediumTest;
9import android.test.suitebuilder.annotation.SmallTest;
10
11import org.chromium.android_webview.AwContents;
12import org.chromium.android_webview.test.util.CommonResources;
13import org.chromium.base.ThreadUtils;
14import org.chromium.base.test.util.DisabledTest;
15import org.chromium.base.test.util.Feature;
16import org.chromium.content.browser.NavigationEntry;
17import org.chromium.content.browser.NavigationHistory;
18import org.chromium.content.browser.test.util.HistoryUtils;
19import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
20import org.chromium.net.test.util.TestWebServer;
21
22import java.util.concurrent.Callable;
23
24public class NavigationHistoryTest extends AwTestBase {
25
26    private static final String PAGE_1_PATH = "/page1.html";
27    private static final String PAGE_1_TITLE = "Page 1 Title";
28    private static final String PAGE_2_PATH = "/page2.html";
29    private static final String PAGE_2_TITLE = "Page 2 Title";
30    private static final String PAGE_WITH_HASHTAG_REDIRECT_TITLE = "Page with hashtag";
31    private static final String LOGIN_PAGE_PATH = "/login.html";
32    private static final String LOGIN_PAGE_TITLE = "Login page";
33    private static final String LOGIN_RESPONSE_PAGE_PATH = "/login-response.html";
34    private static final String LOGIN_RESPONSE_PAGE_TITLE = "Login response";
35    private static final String LOGIN_RESPONSE_PAGE_HELP_LINK_ID = "help";
36
37    private TestWebServer mWebServer;
38    private TestAwContentsClient mContentsClient;
39    private AwContents mAwContents;
40
41    @Override
42    public void setUp() throws Exception {
43        super.setUp();
44        mContentsClient = new TestAwContentsClient();
45        final AwTestContainerView testContainerView =
46            createAwTestContainerViewOnMainSync(mContentsClient);
47        mAwContents = testContainerView.getAwContents();
48        mWebServer = new TestWebServer(false);
49    }
50
51    @Override
52    public void tearDown() throws Exception {
53        mWebServer.shutdown();
54        super.tearDown();
55    }
56
57    private NavigationHistory getNavigationHistory(final AwContents awContents)
58            throws Exception {
59        return ThreadUtils.runOnUiThreadBlocking(new Callable<NavigationHistory>() {
60            @Override
61            public NavigationHistory call() {
62                return awContents.getContentViewCore().getNavigationHistory();
63            }
64        });
65    }
66
67    private void checkHistoryItem(NavigationEntry item, String url, String originalUrl,
68            String title, boolean faviconNull) {
69        assertEquals(url, item.getUrl());
70        assertEquals(originalUrl, item.getOriginalUrl());
71        assertEquals(title, item.getTitle());
72        if (faviconNull) {
73            assertNull(item.getFavicon());
74        } else {
75            assertNotNull(item.getFavicon());
76        }
77    }
78
79    private String addPage1ToServer(TestWebServer webServer) {
80        return mWebServer.setResponse(PAGE_1_PATH,
81                CommonResources.makeHtmlPageFrom(
82                        "<title>" + PAGE_1_TITLE + "</title>",
83                        "<div>This is test page 1.</div>"),
84                CommonResources.getTextHtmlHeaders(false));
85    }
86
87    private String addPage2ToServer(TestWebServer webServer) {
88        return mWebServer.setResponse(PAGE_2_PATH,
89                CommonResources.makeHtmlPageFrom(
90                        "<title>" + PAGE_2_TITLE + "</title>",
91                        "<div>This is test page 2.</div>"),
92                CommonResources.getTextHtmlHeaders(false));
93    }
94
95    private String addPageWithHashTagRedirectToServer(TestWebServer webServer) {
96        return mWebServer.setResponse(PAGE_2_PATH,
97                CommonResources.makeHtmlPageFrom(
98                        "<title>" + PAGE_WITH_HASHTAG_REDIRECT_TITLE + "</title>",
99                        "<iframe onLoad=\"location.replace(location.href + '#tag');\" />"),
100                CommonResources.getTextHtmlHeaders(false));
101    }
102
103    @SmallTest
104    public void testNavigateOneUrl() throws Throwable {
105        NavigationHistory history = getNavigationHistory(mAwContents);
106        assertEquals(0, history.getEntryCount());
107
108        final String pageWithHashTagRedirectUrl = addPageWithHashTagRedirectToServer(mWebServer);
109        enableJavaScriptOnUiThread(mAwContents);
110
111        loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
112                pageWithHashTagRedirectUrl);
113
114        history = getNavigationHistory(mAwContents);
115        checkHistoryItem(history.getEntryAtIndex(0),
116                pageWithHashTagRedirectUrl + "#tag",
117                pageWithHashTagRedirectUrl,
118                PAGE_WITH_HASHTAG_REDIRECT_TITLE,
119                true);
120
121        assertEquals(0, history.getCurrentEntryIndex());
122    }
123
124    @SmallTest
125    public void testNavigateTwoUrls() throws Throwable {
126        NavigationHistory list = getNavigationHistory(mAwContents);
127        assertEquals(0, list.getEntryCount());
128
129        final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
130                mContentsClient.getOnPageFinishedHelper();
131        final String page1Url = addPage1ToServer(mWebServer);
132        final String page2Url = addPage2ToServer(mWebServer);
133
134        loadUrlSync(mAwContents, onPageFinishedHelper, page1Url);
135        loadUrlSync(mAwContents, onPageFinishedHelper, page2Url);
136
137        list = getNavigationHistory(mAwContents);
138
139        // Make sure there is a new entry entry the list
140        assertEquals(2, list.getEntryCount());
141
142        // Make sure the first entry is still okay
143        checkHistoryItem(list.getEntryAtIndex(0),
144                page1Url,
145                page1Url,
146                PAGE_1_TITLE,
147                true);
148
149        // Make sure the second entry was added properly
150        checkHistoryItem(list.getEntryAtIndex(1),
151                page2Url,
152                page2Url,
153                PAGE_2_TITLE,
154                true);
155
156        assertEquals(1, list.getCurrentEntryIndex());
157
158    }
159
160    @SmallTest
161    public void testNavigateTwoUrlsAndBack() throws Throwable {
162        final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
163                mContentsClient.getOnPageFinishedHelper();
164        NavigationHistory list = getNavigationHistory(mAwContents);
165        assertEquals(0, list.getEntryCount());
166
167        final String page1Url = addPage1ToServer(mWebServer);
168        final String page2Url = addPage2ToServer(mWebServer);
169
170        loadUrlSync(mAwContents, onPageFinishedHelper, page1Url);
171        loadUrlSync(mAwContents, onPageFinishedHelper, page2Url);
172
173        HistoryUtils.goBackSync(getInstrumentation(), mAwContents.getContentViewCore(),
174                onPageFinishedHelper);
175        list = getNavigationHistory(mAwContents);
176
177        // Make sure the first entry is still okay
178        checkHistoryItem(list.getEntryAtIndex(0),
179                page1Url,
180                page1Url,
181                PAGE_1_TITLE,
182                true);
183
184        // Make sure the second entry is still okay
185        checkHistoryItem(list.getEntryAtIndex(1),
186                page2Url,
187                page2Url,
188                PAGE_2_TITLE,
189                true);
190
191        // Make sure the current index is back to 0
192        assertEquals(0, list.getCurrentEntryIndex());
193    }
194
195    /**
196     * Disabled until favicons are getting fetched when using ContentView.
197     *
198     * @SmallTest
199     * @throws Throwable
200     */
201    @DisabledTest
202    public void testFavicon() throws Throwable {
203        NavigationHistory list = getNavigationHistory(mAwContents);
204
205        mWebServer.setResponseBase64("/" + CommonResources.FAVICON_FILENAME,
206                CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(false));
207        final String url = mWebServer.setResponse("/favicon.html",
208                CommonResources.FAVICON_STATIC_HTML, null);
209
210        assertEquals(0, list.getEntryCount());
211        getAwSettingsOnUiThread(mAwContents).setImagesEnabled(true);
212        loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), url);
213
214        list = getNavigationHistory(mAwContents);
215
216        // Make sure the first entry is still okay.
217        checkHistoryItem(list.getEntryAtIndex(0), url, url, "", false);
218    }
219
220    private String addNoncacheableLoginPageToServer(TestWebServer webServer) {
221        final String submitButtonId = "submit";
222        final String loginPageHtml =
223                "<html>" +
224                "  <head>" +
225                "    <title>" + LOGIN_PAGE_TITLE + "</title>" +
226                "    <script>" +
227                "      function startAction() {" +
228                "        button = document.getElementById('" + submitButtonId + "');" +
229                "        button.click();" +
230                "      }" +
231                "    </script>" +
232                "  </head>" +
233                "  <body onload='setTimeout(startAction, 0)'>" +
234                "    <form action='" + LOGIN_RESPONSE_PAGE_PATH.substring(1) + "' method='post'>" +
235                "      <input type='text' name='login'>" +
236                "      <input id='" + submitButtonId + "' type='submit' value='Submit'>" +
237                "    </form>" +
238                "  </body>" +
239                "</html>";
240        return mWebServer.setResponse(LOGIN_PAGE_PATH,
241                loginPageHtml,
242                CommonResources.getTextHtmlHeaders(true));
243    }
244
245    private String addNoncacheableLoginResponsePageToServer(TestWebServer webServer) {
246        final String loginResponsePageHtml =
247                "<html>" +
248                "  <head>" +
249                "    <title>" + LOGIN_RESPONSE_PAGE_TITLE + "</title>" +
250                "  </head>" +
251                "  <body>" +
252                "    Login incorrect" +
253                "    <div><a id='" + LOGIN_RESPONSE_PAGE_HELP_LINK_ID + "' href='" +
254                PAGE_1_PATH.substring(1) + "'>Help</a></div>'" +
255                "  </body>" +
256                "</html>";
257        return mWebServer.setResponse(LOGIN_RESPONSE_PAGE_PATH,
258                loginResponsePageHtml,
259                CommonResources.getTextHtmlHeaders(true));
260    }
261
262    // This test simulates Google login page behavior. The page is non-cacheable
263    // and uses POST method for submission. It also contains a help link, leading
264    // to another page. We are verifying that it is possible to go back to the
265    // submitted login page after visiting the help page.
266    @MediumTest
267    @Feature({"AndroidWebView"})
268    public void testNavigateBackToNoncacheableLoginPage() throws Throwable {
269        final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
270                mContentsClient.getOnPageFinishedHelper();
271
272        final String loginPageUrl = addNoncacheableLoginPageToServer(mWebServer);
273        final String loginResponsePageUrl = addNoncacheableLoginResponsePageToServer(mWebServer);
274        final String page1Url = addPage1ToServer(mWebServer);
275
276        getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
277        loadUrlSync(mAwContents, onPageFinishedHelper, loginPageUrl);
278        // Since the page performs an async action, we can't rely on callbacks.
279        assertTrue(pollOnUiThread(new Callable<Boolean>() {
280            @Override
281            public Boolean call() {
282                String title = mAwContents.getContentViewCore().getTitle();
283                return LOGIN_RESPONSE_PAGE_TITLE.equals(title);
284            }
285        }));
286        executeJavaScriptAndWaitForResult(mAwContents,
287                mContentsClient,
288                "link = document.getElementById('" + LOGIN_RESPONSE_PAGE_HELP_LINK_ID + "');" +
289                "link.click();");
290        assertTrue(pollOnUiThread(new Callable<Boolean>() {
291            @Override
292            public Boolean call() {
293                String title = mAwContents.getContentViewCore().getTitle();
294                return PAGE_1_TITLE.equals(title);
295            }
296        }));
297        // Verify that we can still go back to the login response page despite that
298        // it is non-cacheable.
299        HistoryUtils.goBackSync(getInstrumentation(), mAwContents.getContentViewCore(),
300                onPageFinishedHelper);
301        assertTrue(pollOnUiThread(new Callable<Boolean>() {
302            @Override
303            public Boolean call() {
304                String title = mAwContents.getContentViewCore().getTitle();
305                return LOGIN_RESPONSE_PAGE_TITLE.equals(title);
306            }
307        }));
308    }
309}
310