blob: ecfd2ec13b75da0699d47d5632e595a65ed602fb [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.webkit.cts;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import android.os.Message;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Base64;
import android.util.Log;
import android.webkit.ConsoleMessage;
import android.webkit.SslErrorHandler;
import android.webkit.WebChromeClient;
import android.webkit.WebIconDatabase;
import android.webkit.WebResourceResponse;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebSettings.TextSize;
import android.webkit.WebStorage;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
import com.android.compatibility.common.util.NullWebViewUtils;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.CddTest;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Tests for {@link android.webkit.WebSettings}
*/
@CddTest(requirement="3.4.1/H-0-1,T-0-1,A-0-1")
public class WebSettingsTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
private static final int WEBVIEW_TIMEOUT = 5000;
private static final String LOG_TAG = "WebSettingsTest";
private final String EMPTY_IMAGE_HEIGHT = "0";
private final String NETWORK_IMAGE_HEIGHT = "48"; // See getNetworkImageHtml()
private final String DATA_URL_IMAGE_HTML = "<html>" +
"<head><script>function updateTitle(){" +
"document.title=document.getElementById('img').naturalHeight;}</script></head>" +
"<body onload='updateTitle()'>" +
"<img id='img' onload='updateTitle()' src='data:image/png;base64,iVBORw0KGgoAAA" +
"ANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAAAXNSR0IArs4c6QAAAA1JREFUCB0BAgD9/wAAAAIAAc3j" +
"0SsAAAAASUVORK5CYII=" +
"'></body></html>";
private final String DATA_URL_IMAGE_HEIGHT = "1";
private WebSettings mSettings;
private CtsTestServer mWebServer;
private WebViewOnUiThread mOnUiThread;
private Context mContext;
public WebSettingsTest() {
super("android.webkit.cts", WebViewCtsActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
WebView webview = getActivity().getWebView();
if (webview != null) {
mOnUiThread = new WebViewOnUiThread(this, webview);
mSettings = mOnUiThread.getSettings();
}
mContext = getInstrumentation().getTargetContext();
}
@Override
protected void tearDown() throws Exception {
if (mWebServer != null) {
mWebServer.shutdown();
}
if (mOnUiThread != null) {
mOnUiThread.cleanUp();
}
super.tearDown();
}
/**
* Verifies that the default user agent string follows the format defined in Android
* compatibility definition (tokens in angle brackets are variables, tokens in square
* brackets are optional):
* <p/>
* Mozilla/5.0 (Linux; Android <version>; [<devicemodel>] [Build/<buildID>]; wv)
* AppleWebKit/<major>.<minor> (KHTML, like Gecko) Version/<major>.<minor>
* Chrome/<major>.<minor>.<branch>.<build>[ Mobile] Safari/<major>.<minor>
*/
@CddTest(requirement="3.4.1/C-1-3")
public void testUserAgentString_default() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
checkUserAgentStringHelper(mSettings.getUserAgentString(), true);
}
/**
* Verifies that the useragent testing regex is actually correct, because it's very complex.
*/
public void testUserAgentStringTest() {
// All test UAs share the same prefix and suffix; only the middle part varies.
final String prefix = "Mozilla/5.0 (Linux; Android " + Build.VERSION.RELEASE + "; ";
final String suffix = "wv) AppleWebKit/0.0 (KHTML, like Gecko) Version/4.0 Chrome/0.0.0.0 Safari/0.0";
// Valid cases:
// Both model and build present
checkUserAgentStringHelper(prefix + Build.MODEL + " Build/" + Build.ID + "; " + suffix, true);
// Just model
checkUserAgentStringHelper(prefix + Build.MODEL + "; " + suffix, true);
// Just build
checkUserAgentStringHelper(prefix + "Build/" + Build.ID + "; " + suffix, true);
// Neither
checkUserAgentStringHelper(prefix + suffix, true);
// Invalid cases:
// No space between model and build
checkUserAgentStringHelper(prefix + Build.MODEL + "Build/" + Build.ID + "; " + suffix, false);
// No semicolon after model and/or build
checkUserAgentStringHelper(prefix + Build.MODEL + " Build/" + Build.ID + suffix, false);
checkUserAgentStringHelper(prefix + Build.MODEL + suffix, false);
checkUserAgentStringHelper(prefix + "Build/" + Build.ID + suffix, false);
// Double semicolon when both omitted
checkUserAgentStringHelper(prefix + "; " + suffix, false);
}
/**
* Helper function to validate that a given useragent string is or is not valid.
*/
private void checkUserAgentStringHelper(final String useragent, boolean shouldMatch) {
String expectedRelease;
if ("REL".equals(Build.VERSION.CODENAME)) {
expectedRelease = Pattern.quote(Build.VERSION.RELEASE);
} else {
// Non-release builds don't include real release version, be lenient.
expectedRelease = "[^;]+";
}
// Build expected regex inserting the appropriate variables, as this is easier to
// understand and get right than matching any possible useragent and comparing the
// variables afterward.
final String patternString =
// Release version always has a semicolon after it:
Pattern.quote("Mozilla/5.0 (Linux; Android ") + expectedRelease + ";" +
// Model is optional, but if present must have a space first:
"( " + Pattern.quote(Build.MODEL) + ")?" +
// Build is optional, but if present must have a space first:
"( Build/" + Pattern.quote(Build.ID) + ")?" +
// We want a semicolon before the wv token, but we don't want to have two in a row
// if both model and build are omitted. Lookbehind assertions ensure either:
// - the previous character is a semicolon
// - or the previous character is NOT a semicolon AND a semicolon is added here.
"((?<=;)|(?<!;);)" +
// After that we can just check for " wv)" to finish the platform section:
Pattern.quote(" wv) ") +
// The rest of the expression is browser tokens and is fairly simple:
"AppleWebKit/\\d+\\.\\d+ " +
Pattern.quote("(KHTML, like Gecko) Version/4.0 ") +
"Chrome/\\d+\\.\\d+\\.\\d+\\.\\d+ " +
"(Mobile )?Safari/\\d+\\.\\d+";
final Pattern userAgentExpr = Pattern.compile(patternString);
Matcher patternMatcher = userAgentExpr.matcher(useragent);
if (shouldMatch) {
assertTrue(String.format("CDD(3.4.1/C-1-3) User agent string did not match expected pattern. \n" +
"Expected pattern:\n%s\nActual:\n%s", patternString, useragent),
patternMatcher.find());
} else {
assertFalse(String.format("Known-bad user agent string incorrectly matched. \n" +
"Expected pattern:\n%s\nActual:\n%s", patternString, useragent),
patternMatcher.find());
}
}
public void testAccessUserAgentString() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer();
String url = mWebServer.getUserAgentUrl();
String defaultUserAgent = mSettings.getUserAgentString();
assertNotNull(defaultUserAgent);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals(defaultUserAgent, mOnUiThread.getTitle());
// attempting to set a null string has no effect
mSettings.setUserAgentString(null);
assertEquals(defaultUserAgent, mSettings.getUserAgentString());
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals(defaultUserAgent, mOnUiThread.getTitle());
// attempting to set an empty string has no effect
mSettings.setUserAgentString("");
assertEquals(defaultUserAgent, mSettings.getUserAgentString());
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals(defaultUserAgent, mOnUiThread.getTitle());
String customUserAgent = "Cts/test";
mSettings.setUserAgentString(customUserAgent);
assertEquals(customUserAgent, mSettings.getUserAgentString());
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals(customUserAgent, mOnUiThread.getTitle());
}
public void testAccessAllowFileAccess() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// This test is not compatible with 4.0.3
if ("4.0.3".equals(Build.VERSION.RELEASE)) {
return;
}
assertTrue(mSettings.getAllowFileAccess());
String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.BR_TAG_URL);
mSettings.setAllowFileAccess(false);
assertFalse(mSettings.getAllowFileAccess());
mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
// android_asset URLs should still be loaded when even with file access
// disabled.
assertEquals(TestHtmlConstants.BR_TAG_TITLE, mOnUiThread.getTitle());
// Files on the file system should not be loaded.
mOnUiThread.loadUrlAndWaitForCompletion(TestHtmlConstants.LOCAL_FILESYSTEM_URL);
assertEquals(TestHtmlConstants.WEBPAGE_NOT_AVAILABLE_TITLE, mOnUiThread.getTitle());
}
public void testAccessCacheMode() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
runTestOnUiThread(new Runnable() {
@Override
public void run() {
// getInstance must run on the UI thread
WebIconDatabase iconDb = WebIconDatabase.getInstance();
String dbPath = getActivity().getFilesDir().toString() + "/icons";
iconDb.open(dbPath);
}
});
getInstrumentation().waitForIdleSync();
Thread.sleep(100); // Wait for open to be received on the icon db thread.
assertEquals(WebSettings.LOAD_DEFAULT, mSettings.getCacheMode());
mSettings.setCacheMode(WebSettings.LOAD_CACHE_ELSE_NETWORK);
assertEquals(WebSettings.LOAD_CACHE_ELSE_NETWORK, mSettings.getCacheMode());
final IconListenerClient iconListener = new IconListenerClient();
mOnUiThread.setWebChromeClient(iconListener);
loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return iconListener.mReceivedIcon;
}
}.run();
int firstFetch = mWebServer.getRequestCount();
iconListener.mReceivedIcon = false;
loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return iconListener.mReceivedIcon;
}
}.run();
assertEquals(firstFetch, mWebServer.getRequestCount());
mSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
assertEquals(WebSettings.LOAD_NO_CACHE, mSettings.getCacheMode());
iconListener.mReceivedIcon = false;
loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return iconListener.mReceivedIcon;
}
}.run();
int secondFetch = mWebServer.getRequestCount();
iconListener.mReceivedIcon = false;
loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return iconListener.mReceivedIcon;
}
}.run();
int thirdFetch = mWebServer.getRequestCount();
assertTrue(firstFetch < secondFetch);
assertTrue(secondFetch < thirdFetch);
mSettings.setCacheMode(WebSettings.LOAD_CACHE_ONLY);
assertEquals(WebSettings.LOAD_CACHE_ONLY, mSettings.getCacheMode());
iconListener.mReceivedIcon = false;
loadAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return iconListener.mReceivedIcon;
}
}.run();
assertEquals(thirdFetch, mWebServer.getRequestCount());
}
public void testAccessCursiveFontFamily() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getCursiveFontFamily());
String newCusiveFamily = "Apple Chancery";
mSettings.setCursiveFontFamily(newCusiveFamily);
assertEquals(newCusiveFamily, mSettings.getCursiveFontFamily());
}
public void testAccessFantasyFontFamily() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getFantasyFontFamily());
String newFantasyFamily = "Papyrus";
mSettings.setFantasyFontFamily(newFantasyFamily);
assertEquals(newFantasyFamily, mSettings.getFantasyFontFamily());
}
public void testAccessFixedFontFamily() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getFixedFontFamily());
String newFixedFamily = "Courier";
mSettings.setFixedFontFamily(newFixedFamily);
assertEquals(newFixedFamily, mSettings.getFixedFontFamily());
}
public void testAccessSansSerifFontFamily() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getSansSerifFontFamily());
String newFixedFamily = "Verdana";
mSettings.setSansSerifFontFamily(newFixedFamily);
assertEquals(newFixedFamily, mSettings.getSansSerifFontFamily());
}
public void testAccessSerifFontFamily() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getSerifFontFamily());
String newSerifFamily = "Times";
mSettings.setSerifFontFamily(newSerifFamily);
assertEquals(newSerifFamily, mSettings.getSerifFontFamily());
}
public void testAccessStandardFontFamily() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getStandardFontFamily());
String newStandardFamily = "Times";
mSettings.setStandardFontFamily(newStandardFamily);
assertEquals(newStandardFamily, mSettings.getStandardFontFamily());
}
public void testAccessDefaultFontSize() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
int defaultSize = mSettings.getDefaultFontSize();
assertTrue(defaultSize > 0);
mSettings.setDefaultFontSize(1000);
int maxSize = mSettings.getDefaultFontSize();
// cannot check exact size set, since the implementation caps it at an arbitrary limit
assertTrue(maxSize > defaultSize);
mSettings.setDefaultFontSize(-10);
int minSize = mSettings.getDefaultFontSize();
assertTrue(minSize > 0);
assertTrue(minSize < maxSize);
mSettings.setDefaultFontSize(10);
assertEquals(10, mSettings.getDefaultFontSize());
}
public void testAccessDefaultFixedFontSize() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
int defaultSize = mSettings.getDefaultFixedFontSize();
assertTrue(defaultSize > 0);
mSettings.setDefaultFixedFontSize(1000);
int maxSize = mSettings.getDefaultFixedFontSize();
// cannot check exact size, since the implementation caps it at an arbitrary limit
assertTrue(maxSize > defaultSize);
mSettings.setDefaultFixedFontSize(-10);
int minSize = mSettings.getDefaultFixedFontSize();
assertTrue(minSize > 0);
assertTrue(minSize < maxSize);
mSettings.setDefaultFixedFontSize(10);
assertEquals(10, mSettings.getDefaultFixedFontSize());
}
public void testAccessDefaultTextEncodingName() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getDefaultTextEncodingName());
String newEncodingName = "iso-8859-1";
mSettings.setDefaultTextEncodingName(newEncodingName);
assertEquals(newEncodingName, mSettings.getDefaultTextEncodingName());
}
public void testAccessJavaScriptCanOpenWindowsAutomatically() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setJavaScriptEnabled(true);
mSettings.setSupportMultipleWindows(true);
startWebServer();
final WebView childWebView = mOnUiThread.createWebView();
final CountDownLatch latch = new CountDownLatch(1);
mOnUiThread.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onCreateWindow(
WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(childWebView);
resultMsg.sendToTarget();
latch.countDown();
return true;
}
});
mSettings.setJavaScriptCanOpenWindowsAutomatically(false);
assertFalse(mSettings.getJavaScriptCanOpenWindowsAutomatically());
mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL));
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return "Popup blocked".equals(mOnUiThread.getTitle());
}
}.run();
assertEquals(1, latch.getCount());
mSettings.setJavaScriptCanOpenWindowsAutomatically(true);
assertTrue(mSettings.getJavaScriptCanOpenWindowsAutomatically());
mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL));
assertTrue(latch.await(WEBVIEW_TIMEOUT, TimeUnit.MILLISECONDS));
}
public void testAccessJavaScriptEnabled() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setJavaScriptEnabled(true);
assertTrue(mSettings.getJavaScriptEnabled());
loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return "javascript on".equals(mOnUiThread.getTitle());
}
}.run();
mSettings.setJavaScriptEnabled(false);
assertFalse(mSettings.getJavaScriptEnabled());
loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return "javascript off".equals(mOnUiThread.getTitle());
}
}.run();
}
public void testAccessLayoutAlgorithm() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertEquals(WebSettings.LayoutAlgorithm.NARROW_COLUMNS, mSettings.getLayoutAlgorithm());
mSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
assertEquals(WebSettings.LayoutAlgorithm.NORMAL, mSettings.getLayoutAlgorithm());
mSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN);
assertEquals(WebSettings.LayoutAlgorithm.SINGLE_COLUMN, mSettings.getLayoutAlgorithm());
}
public void testAccessMinimumFontSize() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertEquals(8, mSettings.getMinimumFontSize());
mSettings.setMinimumFontSize(100);
assertEquals(72, mSettings.getMinimumFontSize());
mSettings.setMinimumFontSize(-10);
assertEquals(1, mSettings.getMinimumFontSize());
mSettings.setMinimumFontSize(10);
assertEquals(10, mSettings.getMinimumFontSize());
}
public void testAccessMinimumLogicalFontSize() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertEquals(8, mSettings.getMinimumLogicalFontSize());
mSettings.setMinimumLogicalFontSize(100);
assertEquals(72, mSettings.getMinimumLogicalFontSize());
mSettings.setMinimumLogicalFontSize(-10);
assertEquals(1, mSettings.getMinimumLogicalFontSize());
mSettings.setMinimumLogicalFontSize(10);
assertEquals(10, mSettings.getMinimumLogicalFontSize());
}
public void testAccessPluginsEnabled() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getPluginsEnabled());
mSettings.setPluginsEnabled(true);
assertTrue(mSettings.getPluginsEnabled());
}
public void testOffscreenPreRaster() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getOffscreenPreRaster());
mSettings.setOffscreenPreRaster(true);
assertTrue(mSettings.getOffscreenPreRaster());
}
public void testAccessPluginsPath() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(mSettings.getPluginsPath());
String pluginPath = "pluginPath";
mSettings.setPluginsPath(pluginPath);
// plugin path always empty
assertEquals("", mSettings.getPluginsPath());
}
public void testAccessTextSize() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertEquals(TextSize.NORMAL, mSettings.getTextSize());
mSettings.setTextSize(TextSize.LARGER);
assertEquals(TextSize.LARGER, mSettings.getTextSize());
mSettings.setTextSize(TextSize.LARGEST);
assertEquals(TextSize.LARGEST, mSettings.getTextSize());
mSettings.setTextSize(TextSize.SMALLER);
assertEquals(TextSize.SMALLER, mSettings.getTextSize());
mSettings.setTextSize(TextSize.SMALLEST);
assertEquals(TextSize.SMALLEST, mSettings.getTextSize());
}
public void testAccessUseDoubleTree() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getUseDoubleTree());
mSettings.setUseDoubleTree(true);
// setUseDoubleTree is a no-op
assertFalse(mSettings.getUseDoubleTree());
}
public void testAccessUseWideViewPort() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getUseWideViewPort());
mSettings.setUseWideViewPort(true);
assertTrue(mSettings.getUseWideViewPort());
}
public void testSetNeedInitialFocus() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setNeedInitialFocus(false);
mSettings.setNeedInitialFocus(true);
}
public void testSetRenderPriority() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
mSettings.setRenderPriority(WebSettings.RenderPriority.LOW);
mSettings.setRenderPriority(WebSettings.RenderPriority.NORMAL);
}
public void testAccessSupportMultipleWindows() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.supportMultipleWindows());
mSettings.setSupportMultipleWindows(true);
assertTrue(mSettings.supportMultipleWindows());
}
public void testAccessSupportZoom() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertTrue(mSettings.supportZoom());
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mSettings.setSupportZoom(false);
}
});
assertFalse(mSettings.supportZoom());
}
public void testAccessBuiltInZoomControls() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getBuiltInZoomControls());
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mSettings.setBuiltInZoomControls(true);
}
});
assertTrue(mSettings.getBuiltInZoomControls());
}
public void testAppCacheDisabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Test that when AppCache is disabled, we don't get any AppCache
// callbacks.
startWebServer();
final String url = mWebServer.getAppCacheUrl();
mSettings.setJavaScriptEnabled(true);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WEBVIEW_TIMEOUT) {
protected boolean check() {
return "Loaded".equals(mOnUiThread.getTitle());
}
}.run();
// The page is now loaded. Wait for a further 1s to check no AppCache
// callbacks occur.
Thread.sleep(1000);
assertEquals("Loaded", mOnUiThread.getTitle());
}
public void testAppCacheEnabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Note that the AppCache path can only be set once. This limits the
// amount of testing we can do, and means that we must test all aspects
// of setting the AppCache path in a single test to guarantee ordering.
// Test that when AppCache is enabled but no valid path is provided,
// we don't get any AppCache callbacks.
startWebServer();
final String url = mWebServer.getAppCacheUrl();
mSettings.setAppCacheEnabled(true);
mSettings.setJavaScriptEnabled(true);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return "Loaded".equals(mOnUiThread.getTitle());
}
}.run();
// The page is now loaded. Wait for a further 1s to check no AppCache
// callbacks occur.
Thread.sleep(1000);
assertEquals("Loaded", mOnUiThread.getTitle());
// We used to test that when AppCache is enabled and a valid path is
// provided, we got an AppCache callback of some kind, but AppCache is
// deprecated on the web and will be removed from Chromium in the
// future, so this test has been removed.
}
// Ideally, we need a test case for the enabled case. However, it seems that
// enabling the database should happen prior to navigating the first url due to
// some internal limitations of webview. For this reason, we only provide a
// test case for "disabled" behavior.
// Also loading as data rather than using URL should work, but it causes a
// security exception in JS, most likely due to cross domain access. So we load
// using a URL. Finally, it looks like enabling database requires creating a
// webChromeClient and listening to Quota callbacks, which is not documented.
public void testDatabaseDisabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Verify that websql database does not work when disabled.
startWebServer();
mOnUiThread.setWebChromeClient(new ChromeClient(mOnUiThread) {
@Override
public void onExceededDatabaseQuota(String url, String databaseId, long quota,
long estimatedSize, long total, WebStorage.QuotaUpdater updater) {
updater.updateQuota(estimatedSize);
}
});
mSettings.setJavaScriptEnabled(true);
mSettings.setDatabaseEnabled(false);
final String url = mWebServer.getAssetUrl(TestHtmlConstants.DATABASE_ACCESS_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("No database", mOnUiThread.getTitle());
}
public void testLoadsImagesAutomatically() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertTrue(mSettings.getLoadsImagesAutomatically());
startWebServer();
mSettings.setJavaScriptEnabled(true);
// Check that by default network and data url images are loaded.
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle());
// Check that with auto-loading turned off no images are loaded.
// Also check that images are loaded automatically once we enable the setting,
// without reloading the page.
mSettings.setLoadsImagesAutomatically(false);
mOnUiThread.clearCache(true);
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
mSettings.setLoadsImagesAutomatically(true);
waitForNonEmptyImage();
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
mSettings.setLoadsImagesAutomatically(false);
mOnUiThread.clearCache(true);
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
mSettings.setLoadsImagesAutomatically(true);
waitForNonEmptyImage();
assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle());
}
public void testBlockNetworkImage() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getBlockNetworkImage());
startWebServer();
mSettings.setJavaScriptEnabled(true);
// Check that by default network and data url images are loaded.
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle());
// Check that only network images are blocked, data url images are still loaded.
// Also check that network images are loaded automatically once we disable the setting,
// without reloading the page.
mSettings.setBlockNetworkImage(true);
mOnUiThread.clearCache(true);
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
mSettings.setBlockNetworkImage(false);
waitForNonEmptyImage();
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
mSettings.setBlockNetworkImage(true);
mOnUiThread.clearCache(true);
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle());
}
public void testBlockNetworkLoads() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getBlockNetworkLoads());
startWebServer();
mSettings.setJavaScriptEnabled(true);
// Check that by default network resources and data url images are loaded.
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle());
// Check that only network resources are blocked, data url images are still loaded.
mSettings.setBlockNetworkLoads(true);
mOnUiThread.clearCache(true);
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(EMPTY_IMAGE_HEIGHT, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(DATA_URL_IMAGE_HTML, "text/html", null);
assertEquals(DATA_URL_IMAGE_HEIGHT, mOnUiThread.getTitle());
// Check that network resources are loaded once we disable the setting and reload the page.
mSettings.setBlockNetworkLoads(false);
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
mOnUiThread.loadDataAndWaitForCompletion(getNetworkImageHtml(), "text/html", null);
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
}
// Verify that an image in local file system can be loaded by an asset
public void testLocalImageLoads() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setJavaScriptEnabled(true);
// Check that local images are loaded without issues regardless of domain checkings
mSettings.setAllowUniversalAccessFromFileURLs(false);
mSettings.setAllowFileAccessFromFileURLs(false);
String url = TestHtmlConstants.getFileUrl(TestHtmlConstants.IMAGE_ACCESS_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
waitForNonEmptyImage();
assertEquals(NETWORK_IMAGE_HEIGHT, mOnUiThread.getTitle());
}
// Verify that javascript cross-domain request permissions matches file domain settings
// for iframes
public void testIframesWhenAccessFromFileURLsEnabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setJavaScriptEnabled(true);
// disable universal access from files
mSettings.setAllowUniversalAccessFromFileURLs(false);
mSettings.setAllowFileAccessFromFileURLs(true);
// when cross file scripting is enabled, make sure cross domain requests succeed
String url = TestHtmlConstants.getFileUrl(TestHtmlConstants.IFRAME_ACCESS_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
String iframeUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.HELLO_WORLD_URL);
assertEquals(iframeUrl, mOnUiThread.getTitle());
}
// Verify that javascript cross-domain request permissions matches file domain settings
// for iframes
public void testIframesWhenAccessFromFileURLsDisabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mSettings.setJavaScriptEnabled(true);
// disable universal access from files
mSettings.setAllowUniversalAccessFromFileURLs(false);
mSettings.setAllowFileAccessFromFileURLs(false);
// when cross file scripting is disabled, make sure cross domain requests fail
final ChromeClient webChromeClient = new ChromeClient(mOnUiThread);
mOnUiThread.setWebChromeClient(webChromeClient);
String url = TestHtmlConstants.getFileUrl(TestHtmlConstants.IFRAME_ACCESS_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
String iframeUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.HELLO_WORLD_URL);
assertFalse(iframeUrl.equals(mOnUiThread.getTitle()));
assertEquals(ConsoleMessage.MessageLevel.ERROR, webChromeClient.getMessageLevel(10000));
}
// Verify that enabling file access from file URLs enable XmlHttpRequest (XHR) across files
public void testXHRWhenAccessFromFileURLsEnabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
verifyFileXHR(true);
}
// Verify that disabling file access from file URLs disable XmlHttpRequest (XHR) accross files
public void testXHRWhenAccessFromFileURLsDisabled() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final ChromeClient webChromeClient = new ChromeClient(mOnUiThread);
mOnUiThread.setWebChromeClient(webChromeClient);
verifyFileXHR(false);
assertEquals(ConsoleMessage.MessageLevel.ERROR, webChromeClient.getMessageLevel(10000));
}
// verify XHR across files matches the allowFileAccessFromFileURLs setting
private void verifyFileXHR(boolean enableXHR) throws Throwable {
// target file content
String target ="<html><body>target</body><html>";
String targetPath = mContext.getFileStreamPath("target.html").getAbsolutePath();
// local file content that use XHR to read the target file
String local ="" +
"<html><body><script>" +
"var client = new XMLHttpRequest();" +
"client.open('GET', 'file://" + targetPath + "',false);" +
"client.send();" +
"document.title = client.responseText;" +
"</script></body></html>";
// create files in internal storage
writeFile("local.html", local);
writeFile("target.html", target);
mSettings.setJavaScriptEnabled(true);
// disable universal access from files
mSettings.setAllowUniversalAccessFromFileURLs(false);
mSettings.setAllowFileAccessFromFileURLs(enableXHR);
String localPath = mContext.getFileStreamPath("local.html").getAbsolutePath();
// when cross file scripting is enabled, make sure cross domain requests succeed
mOnUiThread.loadUrlAndWaitForCompletion("file://" + localPath);
if (enableXHR) assertEquals(target, mOnUiThread.getTitle());
else assertFalse(target.equals(mOnUiThread.getTitle()));
}
// Create a private file on internal storage from the given string
private void writeFile(String filename, String content) throws Exception {
FileOutputStream fos = mContext.openFileOutput(filename, Context.MODE_PRIVATE);
fos.write(content.getBytes());
fos.close();
}
public void testAllowMixedMode() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String INSECURE_BASE_URL = "http://www.example.com/";
final String INSECURE_JS_URL = INSECURE_BASE_URL + "insecure.js";
final String INSECURE_IMG_URL = INSECURE_BASE_URL + "insecure.png";
final String SECURE_URL = "/secure.html";
final String JS_HTML = "<script src=\"" + INSECURE_JS_URL + "\"></script>";
final String IMG_HTML = "<img src=\"" + INSECURE_IMG_URL + "\" />";
final String SECURE_HTML = "<body>" + IMG_HTML + " " + JS_HTML + "</body>";
final String JS_CONTENT = "window.loaded_js = 42;";
final String IMG_CONTENT = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
final class InterceptClient extends WaitForLoadedClient {
public int mInsecureJsCounter;
public int mInsecureImgCounter;
public InterceptClient() {
super(mOnUiThread);
}
@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
handler.proceed();
}
@Override
public WebResourceResponse shouldInterceptRequest(
WebView view, WebResourceRequest request) {
if (request.getUrl().toString().equals(INSECURE_JS_URL)) {
mInsecureJsCounter++;
return new WebResourceResponse("text/javascript", "utf-8",
new ByteArrayInputStream(JS_CONTENT.getBytes(StandardCharsets.UTF_8)));
} else if (request.getUrl().toString().equals(INSECURE_IMG_URL)) {
mInsecureImgCounter++;
return new WebResourceResponse("image/gif", "utf-8",
new ByteArrayInputStream(Base64.decode(IMG_CONTENT, Base64.DEFAULT)));
}
if (request.getUrl().toString().startsWith(INSECURE_BASE_URL)) {
return new WebResourceResponse("text/html", "UTF-8", null);
}
return null;
}
}
InterceptClient interceptClient = new InterceptClient();
mOnUiThread.setWebViewClient(interceptClient);
mSettings.setJavaScriptEnabled(true);
TestWebServer httpsServer = null;
try {
httpsServer = new TestWebServer(true);
String secureUrl = httpsServer.setResponse(SECURE_URL, SECURE_HTML, null);
mOnUiThread.clearSslPreferences();
mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
mOnUiThread.loadUrlAndWaitForCompletion(secureUrl);
assertEquals(1, httpsServer.getRequestCount(SECURE_URL));
assertEquals(0, interceptClient.mInsecureJsCounter);
assertEquals(0, interceptClient.mInsecureImgCounter);
mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
mOnUiThread.loadUrlAndWaitForCompletion(secureUrl);
assertEquals(2, httpsServer.getRequestCount(SECURE_URL));
assertEquals(1, interceptClient.mInsecureJsCounter);
assertEquals(1, interceptClient.mInsecureImgCounter);
mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
mOnUiThread.loadUrlAndWaitForCompletion(secureUrl);
assertEquals(3, httpsServer.getRequestCount(SECURE_URL));
assertEquals(1, interceptClient.mInsecureJsCounter);
assertEquals(2, interceptClient.mInsecureImgCounter);
} finally {
if (httpsServer != null) {
httpsServer.shutdown();
}
}
}
public void testEnableSafeBrowsing() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertFalse(mSettings.getSafeBrowsingEnabled());
mSettings.setSafeBrowsingEnabled(false);
assertFalse(mSettings.getSafeBrowsingEnabled());
}
/**
* Starts the internal web server. The server will be shut down automatically
* during tearDown().
*
* @throws Exception
*/
private void startWebServer() throws Exception {
assertNull(mWebServer);
mWebServer = new CtsTestServer(getActivity(), false);
}
/**
* Load the given asset from the internal web server. Starts the server if
* it is not already running.
*
* @param asset The name of the asset to load.
* @throws Exception
*/
private void loadAssetUrl(String asset) throws Exception {
if (mWebServer == null) {
startWebServer();
}
String url = mWebServer.getAssetUrl(asset);
mOnUiThread.loadUrlAndWaitForCompletion(url);
}
private String getNetworkImageHtml() {
return "<html>" +
"<head><script>function updateTitle(){" +
"document.title=document.getElementById('img').naturalHeight;}</script></head>" +
"<body onload='updateTitle()'>" +
"<img id='img' onload='updateTitle()' src='" +
mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL) +
"'></body></html>";
}
private void waitForNonEmptyImage() {
new PollingCheck(WEBVIEW_TIMEOUT) {
@Override
protected boolean check() {
return !EMPTY_IMAGE_HEIGHT.equals(mOnUiThread.getTitle());
}
}.run();
}
private class IconListenerClient extends WaitForProgressClient {
public boolean mReceivedIcon;
public IconListenerClient() {
super(mOnUiThread);
}
@Override
public void onReceivedIcon(WebView view, Bitmap icon) {
mReceivedIcon = true;
}
}
}