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 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 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 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 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"}) 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"}) 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"}) 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"}) 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"}) 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"}) 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"}) 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"}) 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"}) 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 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 */ 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"}) 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