| 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 | |
| 5 | package org.chromium.android_webview.test; |
| 6 | |
| 7 | import android.test.FlakyTest; |
| 8 | import android.test.suitebuilder.annotation.MediumTest; |
| 9 | import android.test.suitebuilder.annotation.SmallTest; |
| 10 | |
| 11 | import org.chromium.android_webview.AwContents; |
| 12 | import org.chromium.android_webview.test.util.CommonResources; |
| 13 | import org.chromium.base.ThreadUtils; |
| 14 | import org.chromium.base.test.util.DisabledTest; |
| 15 | import org.chromium.base.test.util.Feature; |
| 16 | import org.chromium.content.browser.NavigationEntry; |
| 17 | import org.chromium.content.browser.NavigationHistory; |
| 18 | import org.chromium.content.browser.test.util.HistoryUtils; |
| 19 | import org.chromium.content.browser.test.util.TestCallbackHelperContainer; |
| 20 | import org.chromium.net.test.util.TestWebServer; |
| 21 | |
| 22 | import java.util.concurrent.Callable; |
| 23 | |
| 24 | public 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 | } |