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 5 package org.chromium.android_webview.test; 6 7 import android.graphics.Bitmap; 8 import android.test.suitebuilder.annotation.SmallTest; 9 10 import org.chromium.android_webview.AwContents; 11 import org.chromium.android_webview.AwSettings; 12 import org.chromium.android_webview.test.util.CommonResources; 13 import org.chromium.base.test.util.DisabledTest; 14 import org.chromium.base.test.util.Feature; 15 import org.chromium.content.browser.test.util.HistoryUtils; 16 import org.chromium.content.browser.test.util.TestCallbackHelperContainer; 17 import org.chromium.content_public.browser.WebContents; 18 import org.chromium.net.test.util.TestWebServer; 19 20 import java.io.File; 21 import java.io.FileOutputStream; 22 import java.util.concurrent.Callable; 23 24 /** 25 * Tests for the {@link android.webkit.WebView#loadDataWithBaseURL(String, String, String, String, 26 * String)} method. 27 */ 28 public class LoadDataWithBaseUrlTest extends AwTestBase { 29 30 private TestAwContentsClient mContentsClient; 31 private AwContents mAwContents; 32 private WebContents mWebContents; 33 34 @Override setUp()35 public void setUp() throws Exception { 36 super.setUp(); 37 mContentsClient = new TestAwContentsClient(); 38 final AwTestContainerView testContainerView = 39 createAwTestContainerViewOnMainSync(mContentsClient); 40 mAwContents = testContainerView.getAwContents(); 41 mWebContents = mAwContents.getWebContents(); 42 } 43 loadDataWithBaseUrlSync( final String data, final String mimeType, final boolean isBase64Encoded, final String baseUrl, final String historyUrl)44 protected void loadDataWithBaseUrlSync( 45 final String data, final String mimeType, final boolean isBase64Encoded, 46 final String baseUrl, final String historyUrl) throws Throwable { 47 loadDataWithBaseUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), 48 data, mimeType, isBase64Encoded, baseUrl, historyUrl); 49 } 50 51 private static final String SCRIPT_FILE = "/script.js"; 52 private static final String SCRIPT_LOADED = "Loaded"; 53 private static final String SCRIPT_NOT_LOADED = "Not loaded"; 54 private static final String SCRIPT_JS = "script_was_loaded = true;"; 55 getScriptFileTestPageHtml(final String scriptUrl)56 private String getScriptFileTestPageHtml(final String scriptUrl) { 57 return "<html>" + 58 " <head>" + 59 " <title>" + SCRIPT_NOT_LOADED + "</title>" + 60 " <script src='" + scriptUrl + "'></script>" + 61 " </head>" + 62 " <body onload=\"if(script_was_loaded) document.title='" + SCRIPT_LOADED + "'\">" + 63 " </body>" + 64 "</html>"; 65 } 66 getCrossOriginAccessTestPageHtml(final String iframeUrl)67 private String getCrossOriginAccessTestPageHtml(final String iframeUrl) { 68 return "<html>" + 69 " <head>" + 70 " <script>" + 71 " function onload() {" + 72 " try {" + 73 " document.title = " + 74 " document.getElementById('frame').contentWindow.location.href;" + 75 " } catch (e) {" + 76 " document.title = 'Exception';" + 77 " }" + 78 " }" + 79 " </script>" + 80 " </head>" + 81 " <body onload='onload()'>" + 82 " <iframe id='frame' src='" + iframeUrl + "'></iframe>" + 83 " </body>" + 84 "</html>"; 85 } 86 87 88 @SmallTest 89 @Feature({"AndroidWebView"}) testImageLoad()90 public void testImageLoad() throws Throwable { 91 TestWebServer webServer = null; 92 try { 93 webServer = new TestWebServer(false); 94 webServer.setResponseBase64("/" + CommonResources.FAVICON_FILENAME, 95 CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true)); 96 97 AwSettings contentSettings = getAwSettingsOnUiThread(mAwContents); 98 contentSettings.setImagesEnabled(true); 99 contentSettings.setJavaScriptEnabled(true); 100 101 loadDataWithBaseUrlSync( 102 CommonResources.getOnImageLoadedHtml(CommonResources.FAVICON_FILENAME), 103 "text/html", false, webServer.getBaseUrl(), null); 104 105 assertEquals("5", getTitleOnUiThread(mAwContents)); 106 } finally { 107 if (webServer != null) webServer.shutdown(); 108 } 109 } 110 111 @SmallTest 112 @Feature({"AndroidWebView"}) testScriptLoad()113 public void testScriptLoad() throws Throwable { 114 TestWebServer webServer = null; 115 try { 116 webServer = new TestWebServer(false); 117 118 final String scriptUrl = webServer.setResponse(SCRIPT_FILE, SCRIPT_JS, 119 CommonResources.getTextJavascriptHeaders(true)); 120 final String pageHtml = getScriptFileTestPageHtml(scriptUrl); 121 122 getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true); 123 loadDataWithBaseUrlSync(pageHtml, "text/html", false, webServer.getBaseUrl(), null); 124 assertEquals(SCRIPT_LOADED, getTitleOnUiThread(mAwContents)); 125 126 } finally { 127 if (webServer != null) webServer.shutdown(); 128 } 129 } 130 131 @SmallTest 132 @Feature({"AndroidWebView"}) testSameOrigin()133 public void testSameOrigin() throws Throwable { 134 TestWebServer webServer = null; 135 try { 136 webServer = new TestWebServer(false); 137 final String frameUrl = webServer.setResponse("/" + CommonResources.ABOUT_FILENAME, 138 CommonResources.ABOUT_HTML, CommonResources.getTextHtmlHeaders(true)); 139 final String html = getCrossOriginAccessTestPageHtml(frameUrl); 140 141 getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true); 142 loadDataWithBaseUrlSync(html, "text/html", false, webServer.getBaseUrl(), null); 143 assertEquals(frameUrl, getTitleOnUiThread(mAwContents)); 144 145 } finally { 146 if (webServer != null) webServer.shutdown(); 147 } 148 } 149 150 @SmallTest 151 @Feature({"AndroidWebView"}) testCrossOrigin()152 public void testCrossOrigin() throws Throwable { 153 TestWebServer webServer = null; 154 try { 155 webServer = new TestWebServer(false); 156 final String frameUrl = webServer.setResponse("/" + CommonResources.ABOUT_FILENAME, 157 CommonResources.ABOUT_HTML, CommonResources.getTextHtmlHeaders(true)); 158 final String html = getCrossOriginAccessTestPageHtml(frameUrl); 159 final String baseUrl = webServer.getBaseUrl().replaceFirst("localhost", "127.0.0.1"); 160 161 getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true); 162 loadDataWithBaseUrlSync(html, "text/html", false, baseUrl, null); 163 164 assertEquals("Exception", getTitleOnUiThread(mAwContents)); 165 166 } finally { 167 if (webServer != null) webServer.shutdown(); 168 } 169 } 170 171 @SmallTest 172 @Feature({"AndroidWebView"}) testNullBaseUrl()173 public void testNullBaseUrl() throws Throwable { 174 getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true); 175 final String pageHtml = "<html><body onload='document.title=document.location.href'>" + 176 "</body></html>"; 177 loadDataWithBaseUrlSync(pageHtml, "text/html", false, null, null); 178 assertEquals("about:blank", getTitleOnUiThread(mAwContents)); 179 } 180 181 @SmallTest 182 @Feature({"AndroidWebView"}) testloadDataWithBaseUrlCallsOnPageStarted()183 public void testloadDataWithBaseUrlCallsOnPageStarted() throws Throwable { 184 final String baseUrl = "http://base.com/"; 185 TestCallbackHelperContainer.OnPageStartedHelper onPageStartedHelper = 186 mContentsClient.getOnPageStartedHelper(); 187 final int callCount = onPageStartedHelper.getCallCount(); 188 loadDataWithBaseUrlAsync(mAwContents, CommonResources.ABOUT_HTML, "text/html", false, 189 baseUrl, "about:blank"); 190 onPageStartedHelper.waitForCallback(callCount); 191 assertEquals(baseUrl, onPageStartedHelper.getUrl()); 192 } 193 194 @SmallTest 195 @Feature({"AndroidWebView"}) testHistoryUrl()196 public void testHistoryUrl() throws Throwable { 197 198 final String pageHtml = "<html><body>Hello, world!</body></html>"; 199 final String baseUrl = "http://example.com"; 200 // TODO(mnaganov): Use the same string as Android CTS suite uses 201 // once GURL issue is resolved (http://code.google.com/p/google-url/issues/detail?id=29) 202 final String historyUrl = "http://history.com/"; 203 loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, historyUrl); 204 assertEquals(historyUrl, HistoryUtils.getUrlOnUiThread( 205 getInstrumentation(), mWebContents)); 206 207 loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, null); 208 assertEquals("about:blank", HistoryUtils.getUrlOnUiThread( 209 getInstrumentation(), mWebContents)); 210 } 211 212 @SmallTest 213 @Feature({"AndroidWebView"}) testOnPageFinishedUrlIsBaseUrl()214 public void testOnPageFinishedUrlIsBaseUrl() throws Throwable { 215 final String pageHtml = "<html><body>Hello, world!</body></html>"; 216 final String baseUrl = "http://example.com/"; 217 loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, baseUrl); 218 loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, baseUrl); 219 TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 220 mContentsClient.getOnPageFinishedHelper(); 221 assertEquals(baseUrl, onPageFinishedHelper.getUrl()); 222 } 223 224 @SmallTest 225 @Feature({"AndroidWebView"}) testHistoryUrlIgnoredWithDataSchemeBaseUrl()226 public void testHistoryUrlIgnoredWithDataSchemeBaseUrl() throws Throwable { 227 final String pageHtml = "<html><body>bar</body></html>"; 228 final String historyUrl = "http://history.com/"; 229 loadDataWithBaseUrlSync(pageHtml, "text/html", false, "data:foo", historyUrl); 230 assertEquals("data:text/html," + pageHtml, HistoryUtils.getUrlOnUiThread( 231 getInstrumentation(), mWebContents)); 232 } 233 234 /* 235 @SmallTest 236 @Feature({"AndroidWebView"}) 237 http://crbug.com/173274 238 */ 239 @DisabledTest testHistoryUrlNavigation()240 public void testHistoryUrlNavigation() throws Throwable { 241 TestWebServer webServer = null; 242 try { 243 webServer = new TestWebServer(false); 244 final String historyUrl = webServer.setResponse("/" + CommonResources.ABOUT_FILENAME, 245 CommonResources.ABOUT_HTML, CommonResources.getTextHtmlHeaders(true)); 246 247 final String page1Title = "Page1"; 248 final String page1Html = "<html><head><title>" + page1Title + "</title>" + 249 "<body>" + page1Title + "</body></html>"; 250 251 loadDataWithBaseUrlSync(page1Html, "text/html", false, null, historyUrl); 252 assertEquals(page1Title, getTitleOnUiThread(mAwContents)); 253 254 final String page2Title = "Page2"; 255 final String page2Html = "<html><head><title>" + page2Title + "</title>" + 256 "<body>" + page2Title + "</body></html>"; 257 258 final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper = 259 mContentsClient.getOnPageFinishedHelper(); 260 loadDataSync(mAwContents, onPageFinishedHelper, page2Html, "text/html", false); 261 assertEquals(page2Title, getTitleOnUiThread(mAwContents)); 262 263 HistoryUtils.goBackSync(getInstrumentation(), mWebContents, onPageFinishedHelper); 264 // The title of the 'about.html' specified via historyUrl. 265 assertEquals(CommonResources.ABOUT_TITLE, getTitleOnUiThread(mAwContents)); 266 267 } finally { 268 if (webServer != null) webServer.shutdown(); 269 } 270 } 271 272 /** 273 * @return true if |fileUrl| was accessible from a data url with |baseUrl| as it's 274 * base URL. 275 */ canAccessFileFromData(String baseUrl, String fileUrl)276 private boolean canAccessFileFromData(String baseUrl, String fileUrl) throws Throwable { 277 final String imageLoaded = "LOADED"; 278 final String imageNotLoaded = "NOT_LOADED"; 279 String data = "<html><body>" + 280 "<img src=\"" + fileUrl + "\" " + 281 "onload=\"document.title=\'" + imageLoaded + "\';\" " + 282 "onerror=\"document.title=\'" + imageNotLoaded + "\';\" />" + 283 "</body></html>"; 284 285 loadDataWithBaseUrlSync(data, "text/html", false, baseUrl, null); 286 287 poll(new Callable<Boolean>() { 288 @Override 289 public Boolean call() throws Exception { 290 String title = getTitleOnUiThread(mAwContents); 291 return imageLoaded.equals(title) || imageNotLoaded.equals(title); 292 } 293 }); 294 295 return imageLoaded.equals(getTitleOnUiThread(mAwContents)); 296 } 297 298 @SmallTest 299 @Feature({"AndroidWebView"}) testLoadDataWithBaseUrlAccessingFile()300 public void testLoadDataWithBaseUrlAccessingFile() throws Throwable { 301 // Create a temporary file on the filesystem we can try to read. 302 File cacheDir = getActivity().getCacheDir(); 303 File tempImage = File.createTempFile("test_image", ".png", cacheDir); 304 Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565); 305 FileOutputStream fos = new FileOutputStream(tempImage); 306 bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); 307 fos.close(); 308 String imagePath = tempImage.getAbsolutePath(); 309 310 AwSettings contentSettings = getAwSettingsOnUiThread(mAwContents); 311 contentSettings.setImagesEnabled(true); 312 contentSettings.setJavaScriptEnabled(true); 313 314 try { 315 final String dataBaseUrl = "data:"; 316 final String nonDataBaseUrl = "http://example.com"; 317 318 mAwContents.getSettings().setAllowFileAccess(false); 319 String token = "" + System.currentTimeMillis(); 320 // All access to file://, including android_asset and android_res is blocked 321 // with a data: base URL, regardless of AwSettings.getAllowFileAccess(). 322 assertFalse(canAccessFileFromData(dataBaseUrl, 323 "file:///android_asset/asset_icon.png?" + token)); 324 assertFalse(canAccessFileFromData(dataBaseUrl, 325 "file:///android_res/raw/resource_icon.png?" + token)); 326 assertFalse(canAccessFileFromData(dataBaseUrl, "file://" + imagePath + "?" + token)); 327 328 // WebView always has access to android_asset and android_res for non-data 329 // base URLs and can access other file:// URLs based on the value of 330 // AwSettings.getAllowFileAccess(). 331 assertTrue(canAccessFileFromData(nonDataBaseUrl, 332 "file:///android_asset/asset_icon.png?" + token)); 333 assertTrue(canAccessFileFromData(nonDataBaseUrl, 334 "file:///android_res/raw/resource_icon.png?" + token)); 335 assertFalse(canAccessFileFromData(nonDataBaseUrl, 336 "file://" + imagePath + "?" + token)); 337 338 token += "a"; 339 mAwContents.getSettings().setAllowFileAccess(true); 340 // We should still be unable to access any file:// with when loading with a 341 // data: base URL, but we should now be able to access the wider file system 342 // (still restricted by OS-level permission checks) with a non-data base URL. 343 assertFalse(canAccessFileFromData(dataBaseUrl, 344 "file:///android_asset/asset_icon.png?" + token)); 345 assertFalse(canAccessFileFromData(dataBaseUrl, 346 "file:///android_res/raw/resource_icon.png?" + token)); 347 assertFalse(canAccessFileFromData(dataBaseUrl, "file://" + imagePath + "?" + token)); 348 349 assertTrue(canAccessFileFromData(nonDataBaseUrl, 350 "file:///android_asset/asset_icon.png?" + token)); 351 assertTrue(canAccessFileFromData(nonDataBaseUrl, 352 "file:///android_res/raw/resource_icon.png?" + token)); 353 assertTrue(canAccessFileFromData(nonDataBaseUrl, 354 "file://" + imagePath + "?" + token)); 355 } finally { 356 if (!tempImage.delete()) throw new AssertionError(); 357 } 358 } 359 } 360