blob: 60e8eb5a6fef938412e01c988128363c3470de01 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.android_webview.test;
import android.graphics.Bitmap;
import android.test.suitebuilder.annotation.SmallTest;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.AwSettings;
import org.chromium.android_webview.test.util.CommonResources;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Restriction;
import org.chromium.content.browser.test.util.HistoryUtils;
import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
import org.chromium.content_public.browser.WebContents;
import org.chromium.net.test.util.TestWebServer;
import java.io.File;
import java.io.FileOutputStream;
import java.util.concurrent.Callable;
/**
* Tests for the {@link android.webkit.WebView#loadDataWithBaseURL(String, String, String, String,
* String)} method.
*/
public class LoadDataWithBaseUrlTest extends AwTestBase {
private TestAwContentsClient mContentsClient;
private AwContents mAwContents;
private WebContents mWebContents;
@Override
public void setUp() throws Exception {
super.setUp();
mContentsClient = new TestAwContentsClient();
final AwTestContainerView testContainerView =
createAwTestContainerViewOnMainSync(mContentsClient);
mAwContents = testContainerView.getAwContents();
mWebContents = mAwContents.getWebContents();
}
protected void loadDataWithBaseUrlSync(
final String data, final String mimeType, final boolean isBase64Encoded,
final String baseUrl, final String historyUrl) throws Throwable {
loadDataWithBaseUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(),
data, mimeType, isBase64Encoded, baseUrl, historyUrl);
}
private static final String SCRIPT_FILE = "/script.js";
private static final String SCRIPT_LOADED = "Loaded";
private static final String SCRIPT_NOT_LOADED = "Not loaded";
private static final String SCRIPT_JS = "script_was_loaded = true;";
private String getScriptFileTestPageHtml(final String scriptUrl) {
return "<html>"
+ " <head>"
+ " <title>" + SCRIPT_NOT_LOADED + "</title>"
+ " <script src='" + scriptUrl + "'></script>"
+ " </head>"
+ " <body onload=\"if(script_was_loaded) document.title='" + SCRIPT_LOADED + "'\">"
+ " </body>"
+ "</html>";
}
private String getCrossOriginAccessTestPageHtml(final String iframeUrl) {
return "<html>"
+ " <head>"
+ " <script>"
+ " function onload() {"
+ " try {"
+ " document.title = "
+ " document.getElementById('frame').contentWindow.location.href;"
+ " } catch (e) {"
+ " document.title = 'Exception';"
+ " }"
+ " }"
+ " </script>"
+ " </head>"
+ " <body onload='onload()'>"
+ " <iframe id='frame' src='" + iframeUrl + "'></iframe>"
+ " </body>"
+ "</html>";
}
@SmallTest
@Feature({"AndroidWebView"})
public void testImageLoad() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
webServer.setResponseBase64("/" + CommonResources.FAVICON_FILENAME,
CommonResources.FAVICON_DATA_BASE64, CommonResources.getImagePngHeaders(true));
AwSettings contentSettings = getAwSettingsOnUiThread(mAwContents);
contentSettings.setImagesEnabled(true);
contentSettings.setJavaScriptEnabled(true);
loadDataWithBaseUrlSync(
CommonResources.getOnImageLoadedHtml(CommonResources.FAVICON_FILENAME),
"text/html", false, webServer.getBaseUrl(), null);
assertEquals("5", getTitleOnUiThread(mAwContents));
} finally {
webServer.shutdown();
}
}
@SmallTest
@Feature({"AndroidWebView"})
public void testScriptLoad() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String scriptUrl = webServer.setResponse(SCRIPT_FILE, SCRIPT_JS,
CommonResources.getTextJavascriptHeaders(true));
final String pageHtml = getScriptFileTestPageHtml(scriptUrl);
getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
loadDataWithBaseUrlSync(pageHtml, "text/html", false, webServer.getBaseUrl(), null);
assertEquals(SCRIPT_LOADED, getTitleOnUiThread(mAwContents));
} finally {
webServer.shutdown();
}
}
@SmallTest
@Feature({"AndroidWebView"})
public void testSameOrigin() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String frameUrl = webServer.setResponse("/" + CommonResources.ABOUT_FILENAME,
CommonResources.ABOUT_HTML, CommonResources.getTextHtmlHeaders(true));
final String html = getCrossOriginAccessTestPageHtml(frameUrl);
getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
loadDataWithBaseUrlSync(html, "text/html", false, webServer.getBaseUrl(), null);
assertEquals(frameUrl, getTitleOnUiThread(mAwContents));
} finally {
webServer.shutdown();
}
}
@SmallTest
@Feature({"AndroidWebView"})
public void testCrossOrigin() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String frameUrl = webServer.setResponse("/" + CommonResources.ABOUT_FILENAME,
CommonResources.ABOUT_HTML, CommonResources.getTextHtmlHeaders(true));
final String html = getCrossOriginAccessTestPageHtml(frameUrl);
final String baseUrl = webServer.getBaseUrl().replaceFirst("localhost", "127.0.0.1");
getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
loadDataWithBaseUrlSync(html, "text/html", false, baseUrl, null);
assertEquals("Exception", getTitleOnUiThread(mAwContents));
} finally {
webServer.shutdown();
}
}
@SmallTest
@Feature({"AndroidWebView"})
public void testNullBaseUrl() throws Throwable {
getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
final String pageHtml = "<html><body onload='document.title=document.location.href'>"
+ "</body></html>";
loadDataWithBaseUrlSync(pageHtml, "text/html", false, null, null);
assertEquals("about:blank", getTitleOnUiThread(mAwContents));
}
@SmallTest
@Feature({"AndroidWebView"})
public void testInvalidBaseUrl() throws Throwable {
final String invalidBaseUrl = "http://";
getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
loadDataWithBaseUrlSync(
CommonResources.ABOUT_HTML, "text/html", false, invalidBaseUrl, null);
// Verify that the load succeeds. The actual base url is undefined.
assertEquals(CommonResources.ABOUT_TITLE, getTitleOnUiThread(mAwContents));
}
@SmallTest
@Feature({"AndroidWebView"})
public void testloadDataWithBaseUrlCallsOnPageStarted() throws Throwable {
final String baseUrl = "http://base.com/";
TestCallbackHelperContainer.OnPageStartedHelper onPageStartedHelper =
mContentsClient.getOnPageStartedHelper();
final int callCount = onPageStartedHelper.getCallCount();
loadDataWithBaseUrlAsync(mAwContents, CommonResources.ABOUT_HTML, "text/html", false,
baseUrl, "about:blank");
onPageStartedHelper.waitForCallback(callCount);
assertEquals(baseUrl, onPageStartedHelper.getUrl());
}
@SmallTest
@Feature({"AndroidWebView"})
public void testHistoryUrl() throws Throwable {
final String pageHtml = "<html><body>Hello, world!</body></html>";
final String baseUrl = "http://example.com";
// TODO(mnaganov): Use the same string as Android CTS suite uses
// once GURL issue is resolved (http://code.google.com/p/google-url/issues/detail?id=29)
final String historyUrl = "http://history.com/";
loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, historyUrl);
assertEquals(historyUrl, HistoryUtils.getUrlOnUiThread(
getInstrumentation(), mWebContents));
loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, null);
assertEquals("about:blank", HistoryUtils.getUrlOnUiThread(
getInstrumentation(), mWebContents));
}
@SmallTest
@Feature({"AndroidWebView"})
public void testOnPageFinishedUrlIsBaseUrl() throws Throwable {
final String pageHtml = "<html><body>Hello, world!</body></html>";
final String baseUrl = "http://example.com/";
loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, baseUrl);
loadDataWithBaseUrlSync(pageHtml, "text/html", false, baseUrl, baseUrl);
TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
assertEquals(baseUrl, onPageFinishedHelper.getUrl());
}
@SmallTest
@Feature({"AndroidWebView"})
public void testHistoryUrlIgnoredWithDataSchemeBaseUrl() throws Throwable {
final String pageHtml = "<html><body>bar</body></html>";
final String historyUrl = "http://history.com/";
loadDataWithBaseUrlSync(pageHtml, "text/html", false, "data:foo", historyUrl);
assertEquals("data:text/html," + pageHtml, HistoryUtils.getUrlOnUiThread(
getInstrumentation(), mWebContents));
}
@SmallTest
@Feature({"AndroidWebView"})
public void testHistoryUrlNavigation() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
final String historyUrl = webServer.setResponse("/" + CommonResources.ABOUT_FILENAME,
CommonResources.ABOUT_HTML, CommonResources.getTextHtmlHeaders(true));
final String page1Title = "Page1";
final String page1Html = "<html><head><title>" + page1Title + "</title>"
+ "<body>" + page1Title + "</body></html>";
loadDataWithBaseUrlSync(page1Html, "text/html", false, null, historyUrl);
assertEquals(page1Title, getTitleOnUiThread(mAwContents));
final String page2Title = "Page2";
final String page2Html = "<html><head><title>" + page2Title + "</title>"
+ "<body>" + page2Title + "</body></html>";
final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
loadDataSync(mAwContents, onPageFinishedHelper, page2Html, "text/html", false);
assertEquals(page2Title, getTitleOnUiThread(mAwContents));
HistoryUtils.goBackSync(getInstrumentation(), mWebContents, onPageFinishedHelper);
// The title of the 'about.html' specified via historyUrl.
assertEquals(CommonResources.ABOUT_TITLE, getTitleOnUiThread(mAwContents));
} finally {
webServer.shutdown();
}
}
/**
* @return true if |fileUrl| was accessible from a data url with |baseUrl| as it's
* base URL.
*/
private boolean canAccessFileFromData(String baseUrl, String fileUrl) throws Throwable {
final String imageLoaded = "LOADED";
final String imageNotLoaded = "NOT_LOADED";
String data = "<html><body>"
+ "<img src=\"" + fileUrl + "\" "
+ "onload=\"document.title=\'" + imageLoaded + "\';\" "
+ "onerror=\"document.title=\'" + imageNotLoaded + "\';\" />"
+ "</body></html>";
loadDataWithBaseUrlSync(data, "text/html", false, baseUrl, null);
pollInstrumentationThread(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
String title = getTitleOnUiThread(mAwContents);
return imageLoaded.equals(title) || imageNotLoaded.equals(title);
}
});
return imageLoaded.equals(getTitleOnUiThread(mAwContents));
}
@SmallTest
@Feature({"AndroidWebView"})
@SuppressWarnings("Finally")
public void testLoadDataWithBaseUrlAccessingFile() throws Throwable {
// Create a temporary file on the filesystem we can try to read.
File cacheDir = getActivity().getCacheDir();
File tempImage = File.createTempFile("test_image", ".png", cacheDir);
Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
FileOutputStream fos = new FileOutputStream(tempImage);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.close();
String imagePath = tempImage.getAbsolutePath();
AwSettings contentSettings = getAwSettingsOnUiThread(mAwContents);
contentSettings.setImagesEnabled(true);
contentSettings.setJavaScriptEnabled(true);
try {
final String dataBaseUrl = "data:";
final String nonDataBaseUrl = "http://example.com";
mAwContents.getSettings().setAllowFileAccess(false);
String token = "" + System.currentTimeMillis();
// All access to file://, including android_asset and android_res is blocked
// with a data: base URL, regardless of AwSettings.getAllowFileAccess().
assertFalse(canAccessFileFromData(dataBaseUrl,
"file:///android_asset/asset_icon.png?" + token));
assertFalse(canAccessFileFromData(dataBaseUrl,
"file:///android_res/raw/resource_icon.png?" + token));
assertFalse(canAccessFileFromData(dataBaseUrl, "file://" + imagePath + "?" + token));
// WebView always has access to android_asset and android_res for non-data
// base URLs and can access other file:// URLs based on the value of
// AwSettings.getAllowFileAccess().
assertTrue(canAccessFileFromData(nonDataBaseUrl,
"file:///android_asset/asset_icon.png?" + token));
assertTrue(canAccessFileFromData(nonDataBaseUrl,
"file:///android_res/raw/resource_icon.png?" + token));
assertFalse(canAccessFileFromData(nonDataBaseUrl,
"file://" + imagePath + "?" + token));
token += "a";
mAwContents.getSettings().setAllowFileAccess(true);
// We should still be unable to access any file:// with when loading with a
// data: base URL, but we should now be able to access the wider file system
// (still restricted by OS-level permission checks) with a non-data base URL.
assertFalse(canAccessFileFromData(dataBaseUrl,
"file:///android_asset/asset_icon.png?" + token));
assertFalse(canAccessFileFromData(dataBaseUrl,
"file:///android_res/raw/resource_icon.png?" + token));
assertFalse(canAccessFileFromData(dataBaseUrl, "file://" + imagePath + "?" + token));
assertTrue(canAccessFileFromData(nonDataBaseUrl,
"file:///android_asset/asset_icon.png?" + token));
assertTrue(canAccessFileFromData(nonDataBaseUrl,
"file:///android_res/raw/resource_icon.png?" + token));
assertTrue(canAccessFileFromData(nonDataBaseUrl,
"file://" + imagePath + "?" + token));
} finally {
if (!tempImage.delete()) throw new AssertionError();
}
}
/**
* Disallowed from running on Svelte devices due to OOM errors: crbug.com/598013
*/
@SmallTest
@Feature({"AndroidWebView"})
@Restriction(Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE)
public void testLoadLargeData() throws Throwable {
// Chrome only allows URLs up to 2MB in IPC. Test something larger than this.
// Note that the real URI may be significantly large if it gets encoded into
// base64.
final int kDataLength = 5 * 1024 * 1024;
StringBuilder doc = new StringBuilder();
doc.append("<html><head></head><body><!-- ");
int i = doc.length();
doc.setLength(i + kDataLength);
while (i < doc.length()) doc.setCharAt(i++, 'A');
doc.append("--><script>window.gotToEndOfBody=true;</script></body></html>");
enableJavaScriptOnUiThread(mAwContents);
loadDataWithBaseUrlSync(doc.toString(), "text/html", false, null, null);
assertEquals("true", executeJavaScriptAndWaitForResult(mAwContents, mContentsClient,
"window.gotToEndOfBody"));
}
@SmallTest
@Feature({"AndroidWebView"})
public void testOnPageFinishedWhenInterrupted() throws Throwable {
// See crbug.com/594001 -- when a javascript: URL is loaded, the pending entry
// gets discarded and the previous load goes through a different path
// inside NavigationController.
final String pageHtml = "<html><body>Hello, world!</body></html>";
final String baseUrl = "http://example.com/";
final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
final int callCount = onPageFinishedHelper.getCallCount();
loadDataWithBaseUrlAsync(mAwContents, pageHtml, "text/html", false, baseUrl, null);
loadUrlAsync(mAwContents, "javascript:42");
onPageFinishedHelper.waitForCallback(callCount);
assertEquals(baseUrl, onPageFinishedHelper.getUrl());
}
@SmallTest
@Feature({"AndroidWebView"})
public void testOnPageFinishedWithInvalidBaseUrlWhenInterrupted() throws Throwable {
final String pageHtml = CommonResources.ABOUT_HTML;
final String invalidBaseUrl = "http://";
final TestCallbackHelperContainer.OnPageFinishedHelper onPageFinishedHelper =
mContentsClient.getOnPageFinishedHelper();
final int callCount = onPageFinishedHelper.getCallCount();
getAwSettingsOnUiThread(mAwContents).setJavaScriptEnabled(true);
loadDataWithBaseUrlAsync(mAwContents, pageHtml, "text/html", false, invalidBaseUrl, null);
loadUrlAsync(mAwContents, "javascript:42");
onPageFinishedHelper.waitForCallback(callCount);
// Verify that the load succeeds. The actual base url is undefined.
assertEquals(CommonResources.ABOUT_TITLE, getTitleOnUiThread(mAwContents));
}
}