blob: b381d72352b1c9caba53b2edc9847b303fae3690 [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.ContentResolver;
import android.content.Context;
import android.content.res.AssetManager;
import android.cts.util.EvaluateJsResultPollingCheck;
import android.cts.util.NullWebViewUtils;
import android.cts.util.PollingCheck;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Picture;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.os.SystemClock;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentAdapter.LayoutResultCallback;
import android.print.PrintDocumentAdapter.WriteResultCallback;
import android.print.PrintDocumentInfo;
import android.test.ActivityInstrumentationTestCase2;
import android.test.UiThreadTest;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.ConsoleMessage;
import android.webkit.CookieSyncManager;
import android.webkit.DownloadListener;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebView.HitTestResult;
import android.webkit.WebView.PictureListener;
import android.webkit.WebViewClient;
import android.webkit.WebViewDatabase;
import android.webkit.cts.WebViewOnUiThread.WaitForLoadedClient;
import android.webkit.cts.WebViewOnUiThread.WaitForProgressClient;
import android.widget.LinearLayout;
import junit.framework.Assert;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.util.EncodingUtils;
import org.apache.http.util.EntityUtils;
public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
public static final long TEST_TIMEOUT = 20000L;
private static final int INITIAL_PROGRESS = 100;
private static final String X_REQUESTED_WITH = "X-Requested-With";
private static final String PRINTER_TEST_FILE = "print.pdf";
private static final String PDF_PREAMBLE = "%PDF-1";
/**
* This is the minimum number of milliseconds to wait for scrolling to
* start. If no scrolling has started before this timeout then it is
* assumed that no scrolling will happen.
*/
private static final long MIN_SCROLL_WAIT_MS = 1000;
/**
* Once scrolling has started, this is the interval that scrolling
* is checked to see if there is a change. If no scrolling change
* has happened in the given time then it is assumed that scrolling
* has stopped.
*/
private static final long SCROLL_WAIT_INTERVAL_MS = 200;
private WebView mWebView;
private CtsTestServer mWebServer;
private WebViewOnUiThread mOnUiThread;
private WebIconDatabase mIconDb;
public WebViewTest() {
super("com.android.cts.webkit", WebViewCtsActivity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
final WebViewCtsActivity activity = getActivity();
mWebView = activity.getWebView();
if (mWebView != null) {
new PollingCheck() {
@Override
protected boolean check() {
return activity.hasWindowFocus();
}
}.run();
File f = activity.getFileStreamPath("snapshot");
if (f.exists()) {
f.delete();
}
mOnUiThread = new WebViewOnUiThread(this, mWebView);
}
}
@Override
protected void tearDown() throws Exception {
if (mOnUiThread != null) {
mOnUiThread.cleanUp();
}
if (mWebServer != null) {
stopWebServer();
}
if (mIconDb != null) {
mIconDb.removeAllIcons();
mIconDb.close();
mIconDb = null;
}
super.tearDown();
}
private void startWebServer(boolean secure) throws Exception {
assertNull(mWebServer);
mWebServer = new CtsTestServer(getActivity(), secure);
}
private void stopWebServer() throws Exception {
assertNotNull(mWebServer);
ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
ThreadPolicy tmpPolicy = new ThreadPolicy.Builder(oldPolicy)
.permitNetwork()
.build();
StrictMode.setThreadPolicy(tmpPolicy);
mWebServer.shutdown();
mWebServer = null;
StrictMode.setThreadPolicy(oldPolicy);
}
@UiThreadTest
public void testConstructor() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
new WebView(getActivity());
new WebView(getActivity(), null);
new WebView(getActivity(), null, 0);
}
@UiThreadTest
public void testCreatingWebViewCreatesCookieSyncManager() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
new WebView(getActivity());
assertNotNull(CookieSyncManager.getInstance());
}
@UiThreadTest
public void testFindAddress() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
/*
* Info about USPS
* http://en.wikipedia.org/wiki/Postal_address#United_States
* http://www.usps.com/
*/
// full address
assertEquals("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826",
WebView.findAddress("455 LARKSPUR DRIVE CALIFORNIA SPRINGS CALIFORNIA 92826"));
// not an address
assertNull(WebView.findAddress("This is not an address: no town, no state, no zip."));
}
@SuppressWarnings("deprecation")
@UiThreadTest
public void testGetZoomControls() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mWebView.getSettings();
assertTrue(settings.supportZoom());
View zoomControls = mWebView.getZoomControls();
assertNotNull(zoomControls);
// disable zoom support
settings.setSupportZoom(false);
assertFalse(settings.supportZoom());
assertNull(mWebView.getZoomControls());
}
@UiThreadTest
public void testInvokeZoomPicker() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mWebView.getSettings();
assertTrue(settings.supportZoom());
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
mWebView.invokeZoomPicker();
}
public void testZoom() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Pinch zoom is not supported in wrap_content layouts.
mOnUiThread.setLayoutHeightToMatchParent();
final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient();
mOnUiThread.setWebViewClient(webViewClient);
mWebServer = new CtsTestServer(getActivity());
mOnUiThread.loadUrlAndWaitForCompletion(
mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL));
pollingCheckForCanZoomIn();
WebSettings settings = mOnUiThread.getSettings();
settings.setSupportZoom(false);
assertFalse(settings.supportZoom());
float currScale = mOnUiThread.getScale();
float previousScale = currScale;
// can zoom in or out although zoom support is disabled in web settings
assertTrue(mOnUiThread.zoomIn());
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale > previousScale);
assertTrue(mOnUiThread.zoomOut());
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale < previousScale);
mOnUiThread.zoomBy(1.25f); // zoom in
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale > previousScale);
mOnUiThread.zoomBy(0.8f); // zoom out
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale < previousScale);
// enable zoom support
settings.setSupportZoom(true);
assertTrue(settings.supportZoom());
previousScale = mOnUiThread.getScale();
assertTrue(mOnUiThread.zoomIn());
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale > previousScale);
// zoom in until it reaches maximum scale
while (mOnUiThread.zoomIn()) {
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale > previousScale);
}
previousScale = currScale;
// can not zoom in further
assertFalse(mOnUiThread.zoomIn());
// We sleep to assert to the best of our ability
// that a scale change does *not* happen.
Thread.sleep(500);
currScale = mOnUiThread.getScale();
assertEquals(currScale, previousScale);
assertTrue(mOnUiThread.zoomOut());
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale < previousScale);
// zoom out until it reaches minimum scale
while (mOnUiThread.zoomOut()) {
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale < previousScale);
}
previousScale = currScale;
assertFalse(mOnUiThread.zoomOut());
// We sleep to assert to the best of our ability
// that a scale change does *not* happen.
Thread.sleep(500);
currScale = mOnUiThread.getScale();
assertEquals(currScale, previousScale);
mOnUiThread.zoomBy(1.25f);
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale > previousScale);
// zoom in until it reaches maximum scale
while (mOnUiThread.canZoomIn()) {
previousScale = currScale;
mOnUiThread.zoomBy(1.25f);
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale > previousScale);
}
previousScale = currScale;
// We sleep to assert to the best of our ability
// that a scale change does *not* happen.
Thread.sleep(500);
currScale = mOnUiThread.getScale();
assertEquals(currScale, previousScale);
mOnUiThread.zoomBy(0.8f);
previousScale = currScale;
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale < previousScale);
// zoom out until it reaches minimum scale
while (mOnUiThread.canZoomOut()) {
previousScale = currScale;
mOnUiThread.zoomBy(0.8f);
webViewClient.waitForScaleChanged();
currScale = mOnUiThread.getScale();
assertTrue(currScale < previousScale);
}
previousScale = currScale;
// We sleep to assert to the best of our ability
// that a scale change does *not* happen.
Thread.sleep(500);
currScale = mOnUiThread.getScale();
assertEquals(currScale, previousScale);
}
@UiThreadTest
public void testSetScrollBarStyle() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
assertFalse(mWebView.overlayHorizontalScrollbar());
assertFalse(mWebView.overlayVerticalScrollbar());
mWebView.setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
assertTrue(mWebView.overlayHorizontalScrollbar());
assertTrue(mWebView.overlayVerticalScrollbar());
mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
assertFalse(mWebView.overlayHorizontalScrollbar());
assertFalse(mWebView.overlayVerticalScrollbar());
mWebView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
assertTrue(mWebView.overlayHorizontalScrollbar());
assertTrue(mWebView.overlayVerticalScrollbar());
}
@UiThreadTest
public void testScrollBarOverlay() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mWebView.setHorizontalScrollbarOverlay(true);
mWebView.setVerticalScrollbarOverlay(false);
assertTrue(mWebView.overlayHorizontalScrollbar());
assertFalse(mWebView.overlayVerticalScrollbar());
mWebView.setHorizontalScrollbarOverlay(false);
mWebView.setVerticalScrollbarOverlay(true);
assertFalse(mWebView.overlayHorizontalScrollbar());
assertTrue(mWebView.overlayVerticalScrollbar());
}
@UiThreadTest
public void testLoadUrl() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNull(mWebView.getUrl());
assertNull(mWebView.getOriginalUrl());
assertEquals(INITIAL_PROGRESS, mWebView.getProgress());
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals(100, mWebView.getProgress());
assertEquals(url, mWebView.getUrl());
assertEquals(url, mWebView.getOriginalUrl());
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
// verify that the request also includes X-Requested-With header
HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
assertEquals(1, matchingHeaders.length);
Header header = matchingHeaders[0];
assertEquals(mWebView.getContext().getApplicationInfo().packageName, header.getValue());
}
@UiThreadTest
public void testPostUrlWithNonNetworkUrl() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL;
mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]);
// Test if the nonNetworkUrl is loaded
assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mWebView.getTitle());
}
@UiThreadTest
public void testPostUrlWithNetworkUrl() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
final String postDataString = "username=my_username&password=my_password";
final byte[] postData = EncodingUtils.getBytes(postDataString, "BASE64");
mOnUiThread.postUrlAndWaitForCompletion(networkUrl, postData);
HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
// The last request should be POST
assertEquals(request.getRequestLine().getMethod(), "POST");
// The last request should have a request body
assertTrue(request instanceof HttpEntityEnclosingRequest);
HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity();
String entityString = EntityUtils.toString(entity);
assertEquals(entityString, postDataString);
}
@UiThreadTest
public void testLoadUrlDoesNotStripParamsWhenLoadingContentUrls() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
Uri.Builder uriBuilder = new Uri.Builder().scheme(
ContentResolver.SCHEME_CONTENT).authority(MockContentProvider.AUTHORITY);
uriBuilder.appendPath("foo.html").appendQueryParameter("param","bar");
String url = uriBuilder.build().toString();
mOnUiThread.loadUrlAndWaitForCompletion(url);
// verify the parameter is not stripped.
Uri uri = Uri.parse(mWebView.getTitle());
assertEquals("bar", uri.getQueryParameter("param"));
}
@UiThreadTest
public void testAppInjectedXRequestedWithHeaderIsNotOverwritten() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
HashMap<String, String> map = new HashMap<String, String>();
final String requester = "foo";
map.put(X_REQUESTED_WITH, requester);
mOnUiThread.loadUrlAndWaitForCompletion(url, map);
// verify that the request also includes X-Requested-With header
// but is not overwritten by the webview
HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
assertEquals(1, matchingHeaders.length);
Header header = matchingHeaders[0];
assertEquals(requester, header.getValue());
}
@UiThreadTest
public void testAppCanInjectHeadersViaImmutableMap() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
HashMap<String, String> map = new HashMap<String, String>();
final String requester = "foo";
map.put(X_REQUESTED_WITH, requester);
mOnUiThread.loadUrlAndWaitForCompletion(url, Collections.unmodifiableMap(map));
// verify that the request also includes X-Requested-With header
// but is not overwritten by the webview
HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
Header[] matchingHeaders = request.getHeaders(X_REQUESTED_WITH);
assertEquals(1, matchingHeaders.length);
Header header = matchingHeaders[0];
assertEquals(requester, header.getValue());
}
public void testCanInjectHeaders() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String X_FOO = "X-foo";
final String X_FOO_VALUE = "test";
final String X_REFERER = "Referer";
final String X_REFERER_VALUE = "http://www.example.com/";
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
HashMap<String, String> map = new HashMap<String, String>();
map.put(X_FOO, X_FOO_VALUE);
map.put(X_REFERER, X_REFERER_VALUE);
mOnUiThread.loadUrlAndWaitForCompletion(url, map);
HttpRequest request = mWebServer.getLastRequest(TestHtmlConstants.HELLO_WORLD_URL);
for (Map.Entry<String,String> value : map.entrySet()) {
String header = value.getKey();
Header[] matchingHeaders = request.getHeaders(header);
assertEquals("header " + header + " not found", 1, matchingHeaders.length);
assertEquals(value.getValue(), matchingHeaders[0].getValue());
}
}
@SuppressWarnings("deprecation")
@UiThreadTest
public void testGetVisibleTitleHeight() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals(0, mWebView.getVisibleTitleHeight());
}
@UiThreadTest
public void testGetOriginalUrl() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
final String finalUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
final String redirectUrl =
mWebServer.getRedirectingAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
assertNull(mWebView.getUrl());
assertNull(mWebView.getOriginalUrl());
// By default, WebView sends an intent to ask the system to
// handle loading a new URL. We set a WebViewClient as
// WebViewClient.shouldOverrideUrlLoading() returns false, so
// the WebView will load the new URL.
mOnUiThread.setWebViewClient(new WaitForLoadedClient(mOnUiThread));
mOnUiThread.loadUrlAndWaitForCompletion(redirectUrl);
assertEquals(finalUrl, mWebView.getUrl());
assertEquals(redirectUrl, mWebView.getOriginalUrl());
}
public void testStopLoading() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertEquals(INITIAL_PROGRESS, mOnUiThread.getProgress());
startWebServer(false);
String url = mWebServer.getDelayedAssetUrl(TestHtmlConstants.STOP_LOADING_URL);
class JsInterface {
private boolean mPageLoaded;
@JavascriptInterface
public synchronized void pageLoaded() {
mPageLoaded = true;
notify();
}
public synchronized boolean getPageLoaded() {
return mPageLoaded;
}
}
JsInterface jsInterface = new JsInterface();
mOnUiThread.getSettings().setJavaScriptEnabled(true);
mOnUiThread.addJavascriptInterface(jsInterface, "javabridge");
mOnUiThread.loadUrl(url);
mOnUiThread.stopLoading();
// We wait to see that the onload callback in the HTML is not fired.
synchronized (jsInterface) {
jsInterface.wait(3000);
}
assertFalse(jsInterface.getPageLoaded());
}
@UiThreadTest
public void testGoBackAndForward() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertGoBackOrForwardBySteps(false, -1);
assertGoBackOrForwardBySteps(false, 1);
startWebServer(false);
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
pollingCheckWebBackForwardList(url1, 0, 1);
assertGoBackOrForwardBySteps(false, -1);
assertGoBackOrForwardBySteps(false, 1);
mOnUiThread.loadUrlAndWaitForCompletion(url2);
pollingCheckWebBackForwardList(url2, 1, 2);
assertGoBackOrForwardBySteps(true, -1);
assertGoBackOrForwardBySteps(false, 1);
mOnUiThread.loadUrlAndWaitForCompletion(url3);
pollingCheckWebBackForwardList(url3, 2, 3);
assertGoBackOrForwardBySteps(true, -2);
assertGoBackOrForwardBySteps(false, 1);
mWebView.goBack();
pollingCheckWebBackForwardList(url2, 1, 3);
assertGoBackOrForwardBySteps(true, -1);
assertGoBackOrForwardBySteps(true, 1);
mWebView.goForward();
pollingCheckWebBackForwardList(url3, 2, 3);
assertGoBackOrForwardBySteps(true, -2);
assertGoBackOrForwardBySteps(false, 1);
mWebView.goBackOrForward(-2);
pollingCheckWebBackForwardList(url1, 0, 3);
assertGoBackOrForwardBySteps(false, -1);
assertGoBackOrForwardBySteps(true, 2);
mWebView.goBackOrForward(2);
pollingCheckWebBackForwardList(url3, 2, 3);
assertGoBackOrForwardBySteps(true, -2);
assertGoBackOrForwardBySteps(false, 1);
}
@UiThreadTest
public void testAddJavascriptInterface() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
final class DummyJavaScriptInterface {
private boolean mWasProvideResultCalled;
private String mResult;
private synchronized String waitForResult() {
while (!mWasProvideResultCalled) {
try {
wait(TEST_TIMEOUT);
} catch (InterruptedException e) {
continue;
}
if (!mWasProvideResultCalled) {
Assert.fail("Unexpected timeout");
}
}
return mResult;
}
public synchronized boolean wasProvideResultCalled() {
return mWasProvideResultCalled;
}
@JavascriptInterface
public synchronized void provideResult(String result) {
mWasProvideResultCalled = true;
mResult = result;
notify();
}
}
final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
mWebView.addJavascriptInterface(obj, "dummy");
assertFalse(obj.wasProvideResultCalled());
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.ADD_JAVA_SCRIPT_INTERFACE_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("Original title", obj.waitForResult());
// Verify that only methods annotated with @JavascriptInterface are exposed
// on the JavaScript interface object.
mOnUiThread.evaluateJavascript("typeof dummy.provideResult",
new ValueCallback<String>() {
@Override
public void onReceiveValue(String result) {
assertEquals("\"function\"", result);
}
});
mOnUiThread.evaluateJavascript("typeof dummy.wasProvideResultCalled",
new ValueCallback<String>() {
@Override
public void onReceiveValue(String result) {
assertEquals("\"undefined\"", result);
}
});
mOnUiThread.evaluateJavascript("typeof dummy.getClass",
new ValueCallback<String>() {
@Override
public void onReceiveValue(String result) {
assertEquals("\"undefined\"", result);
}
});
}
@UiThreadTest
public void testAddJavascriptInterfaceNullObject() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
String setTitleToPropertyTypeHtml = "<html><head></head>" +
"<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
// Test that the property is initially undefined.
mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
"text/html", null);
assertEquals("undefined", mWebView.getTitle());
// Test that adding a null object has no effect.
mWebView.addJavascriptInterface(null, "injectedObject");
mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
"text/html", null);
assertEquals("undefined", mWebView.getTitle());
// Test that adding an object gives an object type.
final Object obj = new Object();
mWebView.addJavascriptInterface(obj, "injectedObject");
mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
"text/html", null);
assertEquals("object", mWebView.getTitle());
// Test that trying to replace with a null object has no effect.
mWebView.addJavascriptInterface(null, "injectedObject");
mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
"text/html", null);
assertEquals("object", mWebView.getTitle());
}
@UiThreadTest
public void testRemoveJavascriptInterface() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mWebView.getSettings();
settings.setJavaScriptEnabled(true);
String setTitleToPropertyTypeHtml = "<html><head></head>" +
"<body onload=\"document.title = typeof window.injectedObject;\"></body></html>";
// Test that adding an object gives an object type.
mWebView.addJavascriptInterface(new Object(), "injectedObject");
mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
"text/html", null);
assertEquals("object", mWebView.getTitle());
// Test that reloading the page after removing the object leaves the property undefined.
mWebView.removeJavascriptInterface("injectedObject");
mOnUiThread.loadDataAndWaitForCompletion(setTitleToPropertyTypeHtml,
"text/html", null);
assertEquals("undefined", mWebView.getTitle());
}
public void testUseRemovedJavascriptInterface() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
class RemovedObject {
@Override
@JavascriptInterface
public String toString() {
return "removedObject";
}
@JavascriptInterface
public void remove() throws Throwable {
mOnUiThread.removeJavascriptInterface("removedObject");
System.gc();
}
}
class ResultObject {
private String mResult;
private boolean mIsResultAvailable;
@JavascriptInterface
public synchronized void setResult(String result) {
mResult = result;
mIsResultAvailable = true;
notify();
}
public synchronized String getResult() {
while (!mIsResultAvailable) {
try {
wait();
} catch (InterruptedException e) {
}
}
return mResult;
}
}
final ResultObject resultObject = new ResultObject();
// Test that an object is still usable if removed while the page is in use, even if we have
// no external references to it.
mOnUiThread.getSettings().setJavaScriptEnabled(true);
mOnUiThread.addJavascriptInterface(new RemovedObject(), "removedObject");
mOnUiThread.addJavascriptInterface(resultObject, "resultObject");
mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" +
"<body onload=\"window.removedObject.remove();" +
"resultObject.setResult(removedObject.toString());\"></body></html>",
"text/html", null);
assertEquals("removedObject", resultObject.getResult());
}
public void testAddJavascriptInterfaceExceptions() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
settings.setJavaScriptCanOpenWindowsAutomatically(true);
final AtomicBoolean mJsInterfaceWasCalled = new AtomicBoolean(false) {
@JavascriptInterface
public synchronized void call() {
set(true);
// The main purpose of this test is to ensure an exception here does not
// crash the implementation.
throw new RuntimeException("Javascript Interface exception");
}
};
mOnUiThread.addJavascriptInterface(mJsInterfaceWasCalled, "dummy");
mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
assertFalse(mJsInterfaceWasCalled.get());
final CountDownLatch resultLatch = new CountDownLatch(1);
mOnUiThread.evaluateJavascript(
"try {dummy.call(); 'fail'; } catch (exception) { 'pass'; } ",
new ValueCallback<String>() {
@Override
public void onReceiveValue(String result) {
assertEquals("\"pass\"", result);
resultLatch.countDown();
}
});
assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
assertTrue(mJsInterfaceWasCalled.get());
}
public void testJavascriptInterfaceCustomPropertiesClearedOnReload() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mOnUiThread.getSettings().setJavaScriptEnabled(true);
class DummyJavaScriptInterface {
}
final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
mOnUiThread.addJavascriptInterface(obj, "dummy");
mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
EvaluateJsResultPollingCheck jsResult;
jsResult = new EvaluateJsResultPollingCheck("42");
mOnUiThread.evaluateJavascript("dummy.custom_property = 42", jsResult);
jsResult.run();
jsResult = new EvaluateJsResultPollingCheck("true");
mOnUiThread.evaluateJavascript("'custom_property' in dummy", jsResult);
jsResult.run();
mOnUiThread.reload();
jsResult = new EvaluateJsResultPollingCheck("false");
mOnUiThread.evaluateJavascript("'custom_property' in dummy", jsResult);
jsResult.run();
}
public void testJavascriptInterfaceForClientPopup() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mOnUiThread.getSettings().setJavaScriptEnabled(true);
mOnUiThread.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
mOnUiThread.getSettings().setSupportMultipleWindows(true);
class DummyJavaScriptInterface {
@JavascriptInterface
public int test() {
return 42;
}
}
final DummyJavaScriptInterface obj = new DummyJavaScriptInterface();
final WebView childWebView = mOnUiThread.createWebView();
WebViewOnUiThread childOnUiThread = new WebViewOnUiThread(this, childWebView);
childOnUiThread.getSettings().setJavaScriptEnabled(true);
childOnUiThread.addJavascriptInterface(obj, "dummy");
final boolean[] hadOnCreateWindow = new boolean[1];
hadOnCreateWindow[0] = false;
mOnUiThread.setWebChromeClient(new WebViewOnUiThread.WaitForProgressClient(mOnUiThread) {
@Override
public boolean onCreateWindow(
WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
getActivity().addContentView(childWebView, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
transport.setWebView(childWebView);
resultMsg.sendToTarget();
hadOnCreateWindow[0] = true;
return true;
}
});
startWebServer(false);
mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
getAssetUrl(TestHtmlConstants.POPUP_URL));
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return hadOnCreateWindow[0];
}
}.run();
childOnUiThread.loadUrlAndWaitForCompletion("about:blank");
EvaluateJsResultPollingCheck jsResult;
jsResult = new EvaluateJsResultPollingCheck("true");
childOnUiThread.evaluateJavascript("'dummy' in window", jsResult);
jsResult.run();
// Verify that the injected object is functional.
jsResult = new EvaluateJsResultPollingCheck("42");
childOnUiThread.evaluateJavascript("dummy.test()", jsResult);
jsResult.run();
}
private final class TestPictureListener implements PictureListener {
public int callCount;
@Override
public void onNewPicture(WebView view, Picture picture) {
// Need to inform the listener tracking new picture
// for the "page loaded" knowledge since it has been replaced.
mOnUiThread.onNewPicture();
this.callCount += 1;
}
}
private Picture waitForPictureToHaveColor(int color,
final TestPictureListener listener) throws Throwable {
final int MAX_ON_NEW_PICTURE_ITERATIONS = 5;
final AtomicReference<Picture> pictureRef = new AtomicReference<Picture>();
for (int i = 0; i < MAX_ON_NEW_PICTURE_ITERATIONS; i++) {
final int oldCallCount = listener.callCount;
runTestOnUiThread(new Runnable() {
@Override
public void run() {
pictureRef.set(mWebView.capturePicture());
}
});
if (isPictureFilledWithColor(pictureRef.get(), color))
break;
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return listener.callCount > oldCallCount;
}
}.run();
}
return pictureRef.get();
}
public void testCapturePicture() throws Exception, Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final TestPictureListener listener = new TestPictureListener();
startWebServer(false);
final String url = mWebServer.getAssetUrl(TestHtmlConstants.BLANK_PAGE_URL);
mOnUiThread.setPictureListener(listener);
// Showing the blank page will fill the picture with the background color.
mOnUiThread.loadUrlAndWaitForCompletion(url);
// The default background color is white.
Picture oldPicture = waitForPictureToHaveColor(Color.WHITE, listener);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.setBackgroundColor(Color.CYAN);
}
});
mOnUiThread.reloadAndWaitForCompletion();
waitForPictureToHaveColor(Color.CYAN, listener);
// The content of the previously captured picture will not be updated automatically.
assertTrue(isPictureFilledWithColor(oldPicture, Color.WHITE));
}
public void testSetPictureListener() throws Exception, Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final class MyPictureListener implements PictureListener {
public int callCount;
public WebView webView;
public Picture picture;
@Override
public void onNewPicture(WebView view, Picture picture) {
// Need to inform the listener tracking new picture
// for the "page loaded" knowledge since it has been replaced.
mOnUiThread.onNewPicture();
this.callCount += 1;
this.webView = view;
this.picture = picture;
}
}
final MyPictureListener listener = new MyPictureListener();
startWebServer(false);
final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.setPictureListener(listener);
mOnUiThread.loadUrlAndWaitForCompletion(url);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return listener.callCount > 0;
}
}.run();
assertEquals(mWebView, listener.webView);
assertNull(listener.picture);
final int oldCallCount = listener.callCount;
final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
mOnUiThread.loadUrlAndWaitForCompletion(newUrl);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return listener.callCount > oldCallCount;
}
}.run();
}
@UiThreadTest
public void testAccessHttpAuthUsernamePassword() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
try {
WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword();
String host = "http://localhost:8080";
String realm = "testrealm";
String userName = "user";
String password = "password";
String[] result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNull(result);
mWebView.setHttpAuthUsernamePassword(host, realm, userName, password);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertEquals(userName, result[0]);
assertEquals(password, result[1]);
String newPassword = "newpassword";
mWebView.setHttpAuthUsernamePassword(host, realm, userName, newPassword);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertEquals(userName, result[0]);
assertEquals(newPassword, result[1]);
String newUserName = "newuser";
mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertEquals(newUserName, result[0]);
assertEquals(newPassword, result[1]);
// the user is set to null, can not change any thing in the future
mWebView.setHttpAuthUsernamePassword(host, realm, null, password);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertNull(result[0]);
assertEquals(password, result[1]);
mWebView.setHttpAuthUsernamePassword(host, realm, userName, null);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertEquals(userName, result[0]);
assertEquals(null, result[1]);
mWebView.setHttpAuthUsernamePassword(host, realm, null, null);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertNull(result[0]);
assertNull(result[1]);
mWebView.setHttpAuthUsernamePassword(host, realm, newUserName, newPassword);
result = mWebView.getHttpAuthUsernamePassword(host, realm);
assertNotNull(result);
assertEquals(newUserName, result[0]);
assertEquals(newPassword, result[1]);
} finally {
WebViewDatabase.getInstance(getActivity()).clearHttpAuthUsernamePassword();
}
}
public void testLoadData() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String HTML_CONTENT =
"<html><head><title>Hello,World!</title></head><body></body>" +
"</html>";
mOnUiThread.loadDataAndWaitForCompletion(HTML_CONTENT,
"text/html", null);
assertEquals("Hello,World!", mOnUiThread.getTitle());
startWebServer(false);
final ChromeClient webChromeClient = new ChromeClient(mOnUiThread);
final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.getSettings().setJavaScriptEnabled(true);
mOnUiThread.setWebChromeClient(webChromeClient);
mOnUiThread.loadDataAndWaitForCompletion(
"<html><head></head><body onload=\"" +
"document.title = " +
"document.getElementById('frame').contentWindow.location.href;" +
"\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>",
"text/html", null);
}
});
assertEquals(ConsoleMessage.MessageLevel.ERROR, webChromeClient.getMessageLevel(10000));
}
@UiThreadTest
public void testLoadDataWithBaseUrl() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNull(mWebView.getUrl());
String imgUrl = TestHtmlConstants.SMALL_IMG_URL; // relative
// Snippet of HTML that will prevent favicon requests to the test server.
final String HTML_HEADER = "<html><head><link rel=\"shortcut icon\" href=\"#\" /></head>";
// Trying to resolve a relative URL against a data URL without a base URL
// will fail and we won't make a request to the test web server.
// By using the test web server as the base URL we expect to see a request
// for the relative URL in the test server.
startWebServer(false);
String baseUrl = mWebServer.getAssetUrl("foo.html");
String historyUrl = "http://www.example.com/";
mWebServer.resetRequestState();
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>",
"text/html", "UTF-8", historyUrl);
// Verify that the resource request makes it to the server.
assertTrue(mWebServer.wasResourceRequested(imgUrl));
assertEquals(historyUrl, mWebView.getUrl());
// Check that reported URL is "about:blank" when supplied history URL
// is null.
imgUrl = TestHtmlConstants.LARGE_IMG_URL;
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
HTML_HEADER + "<body><img src=\"" + imgUrl + "\"/></body></html>",
"text/html", "UTF-8", null);
assertTrue(mWebServer.wasResourceRequested(imgUrl));
assertEquals("about:blank", mWebView.getUrl());
// Test that JavaScript can access content from the same origin as the base URL.
mWebView.getSettings().setJavaScriptEnabled(true);
final String crossOriginUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(baseUrl,
HTML_HEADER + "<body onload=\"" +
"document.title = document.getElementById('frame').contentWindow.location.href;" +
"\"><iframe id=\"frame\" src=\"" + crossOriginUrl + "\"></body></html>",
"text/html", "UTF-8", null);
assertEquals(crossOriginUrl, mWebView.getTitle());
// Check that when the base URL uses the 'data' scheme, a 'data' scheme URL is used and the
// history URL is ignored.
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
HTML_HEADER + "<body>bar</body></html>", "text/html", "UTF-8",
historyUrl);
assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("data:text/html") == 0);
assertTrue("URL: " + mWebView.getUrl(), mWebView.getUrl().indexOf("bar") > 0);
// Check that when a non-data: base URL is used, we treat the String to load as
// a raw string and just dump it into the WebView, i.e. not decoding any URL entities.
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("http://www.foo.com",
HTML_HEADER + "<title>Hello World%21</title><body>bar</body></html>",
"text/html", "UTF-8", null);
assertEquals("Hello World%21", mOnUiThread.getTitle());
// Check that when a data: base URL is used, we treat the String to load as a data: URL
// and run load steps such as decoding URL entities (i.e., contrary to the test case
// above.)
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("data:foo",
HTML_HEADER + "<title>Hello World%21</title></html>", "text/html", "UTF-8", null);
assertEquals("Hello World!", mOnUiThread.getTitle());
// Check the method is null input safe.
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion(null, null, null, null, null);
assertEquals("about:blank", mOnUiThread.getUrl());
}
private void deleteIfExists(File file) throws IOException {
if (file.exists()) {
file.delete();
}
}
private String readTextFile(File file, Charset encoding)
throws FileNotFoundException, IOException {
FileInputStream stream = new FileInputStream(file);
byte[] bytes = new byte[(int)file.length()];
stream.read(bytes);
stream.close();
return new String(bytes, encoding);
}
private void doSaveWebArchive(String baseName, boolean autoName, final String expectName)
throws Throwable {
final Semaphore saving = new Semaphore(0);
ValueCallback<String> callback = new ValueCallback<String>() {
@Override
public void onReceiveValue(String savedName) {
assertEquals(expectName, savedName);
saving.release();
}
};
mOnUiThread.saveWebArchive(baseName, autoName, callback);
assertTrue(saving.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
}
public void testSaveWebArchive() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String testPage = "testSaveWebArchive test page";
File dir = getActivity().getFilesDir();
String dirStr = dir.toString();
File test = new File(dir, "test.mht");
deleteIfExists(test);
String testStr = test.getAbsolutePath();
File index = new File(dir, "index.mht");
deleteIfExists(index);
String indexStr = index.getAbsolutePath();
File index1 = new File(dir, "index-1.mht");
deleteIfExists(index1);
String index1Str = index1.getAbsolutePath();
mOnUiThread.loadDataAndWaitForCompletion(testPage, "text/html", "UTF-8");
try {
// Save test.mht
doSaveWebArchive(testStr, false, testStr);
// Check the contents of test.mht
String testMhtml = readTextFile(test, StandardCharsets.UTF_8);
assertTrue(testMhtml.contains(testPage));
// Save index.mht
doSaveWebArchive(dirStr + "/", true, indexStr);
// Check the contents of index.mht
String indexMhtml = readTextFile(index, StandardCharsets.UTF_8);
assertTrue(indexMhtml.contains(testPage));
// Save index-1.mht since index.mht already exists
doSaveWebArchive(dirStr + "/", true, index1Str);
// Check the contents of index-1.mht
String index1Mhtml = readTextFile(index1, StandardCharsets.UTF_8);
assertTrue(index1Mhtml.contains(testPage));
// Try a file in a bogus directory
doSaveWebArchive("/bogus/path/test.mht", false, null);
// Try a bogus directory
doSaveWebArchive("/bogus/path/", true, null);
} finally {
deleteIfExists(test);
deleteIfExists(index);
deleteIfExists(index1);
}
}
private static class WaitForFindResultsListener extends FutureTask<Integer>
implements WebView.FindListener {
public WaitForFindResultsListener() {
super(new Runnable() {
@Override
public void run() { }
}, null);
}
@Override
public void onFindResultReceived(int activeMatchOrdinal, int numberOfMatches,
boolean isDoneCounting) {
if (isDoneCounting) {
set(numberOfMatches);
}
}
}
public void testFindAll() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Make the page scrollable, so we can detect the scrolling to make sure the
// content fully loaded.
mOnUiThread.setInitialScale(100);
DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
int dimension = Math.max(metrics.widthPixels, metrics.heightPixels);
// create a paragraph high enough to take up the entire screen
String p = "<p style=\"height:" + dimension + "px;\">" +
"Find all instances of find on the page and highlight them.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
WaitForFindResultsListener l = new WaitForFindResultsListener();
int previousScrollY = mOnUiThread.getScrollY();
mOnUiThread.pageDown(true);
// Wait for content fully loaded.
waitForScrollingComplete(previousScrollY);
mOnUiThread.setFindListener(l);
mOnUiThread.findAll("find");
assertEquals(2, l.get().intValue());
}
public void testFindNext() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Reset the scaling so that finding the next "all" text will require scrolling.
mOnUiThread.setInitialScale(100);
DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
int dimension = Math.max(metrics.widthPixels, metrics.heightPixels);
// create a paragraph high enough to take up the entire screen
String p = "<p style=\"height:" + dimension + "px;\">" +
"Find all instances of a word on the page and highlight them.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p + p + "</body></html>", "text/html", null);
WaitForFindResultsListener l = new WaitForFindResultsListener();
mOnUiThread.setFindListener(l);
// highlight all the strings found
mOnUiThread.findAll("all");
// make sure the findAll action is completed before findNext
l.get();
mOnUiThread.setFindListener(null);
int previousScrollY = mOnUiThread.getScrollY();
// Focus "all" in the second page and assert that the view scrolls.
mOnUiThread.findNext(true);
waitForScrollingComplete(previousScrollY);
assertTrue(mOnUiThread.getScrollY() > previousScrollY);
previousScrollY = mOnUiThread.getScrollY();
// Focus "all" in the first page and assert that the view scrolls.
mOnUiThread.findNext(true);
waitForScrollingComplete(previousScrollY);
assertTrue(mOnUiThread.getScrollY() < previousScrollY);
previousScrollY = mOnUiThread.getScrollY();
// Focus "all" in the second page and assert that the view scrolls.
mOnUiThread.findNext(false);
waitForScrollingComplete(previousScrollY);
assertTrue(mOnUiThread.getScrollY() > previousScrollY);
previousScrollY = mOnUiThread.getScrollY();
// Focus "all" in the first page and assert that the view scrolls.
mOnUiThread.findNext(false);
waitForScrollingComplete(previousScrollY);
assertTrue(mOnUiThread.getScrollY() < previousScrollY);
previousScrollY = mOnUiThread.getScrollY();
// clear the result
mOnUiThread.clearMatches();
getInstrumentation().waitForIdleSync();
// can not scroll any more
mOnUiThread.findNext(false);
waitForScrollingComplete(previousScrollY);
assertTrue(mOnUiThread.getScrollY() == previousScrollY);
mOnUiThread.findNext(true);
waitForScrollingComplete(previousScrollY);
assertTrue(mOnUiThread.getScrollY() == previousScrollY);
}
public void testDocumentHasImages() throws Exception, Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final class DocumentHasImageCheckHandler extends Handler {
private boolean mReceived;
private int mMsgArg1;
public DocumentHasImageCheckHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
synchronized(this) {
mReceived = true;
mMsgArg1 = msg.arg1;
}
}
public synchronized boolean hasCalledHandleMessage() {
return mReceived;
}
public synchronized int getMsgArg1() {
return mMsgArg1;
}
}
startWebServer(false);
final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
// Create a handler on the UI thread.
final DocumentHasImageCheckHandler handler =
new DocumentHasImageCheckHandler(mWebView.getHandler().getLooper());
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mOnUiThread.loadDataAndWaitForCompletion("<html><body><img src=\""
+ imgUrl + "\"/></body></html>", "text/html", null);
Message response = new Message();
response.setTarget(handler);
assertFalse(handler.hasCalledHandleMessage());
mWebView.documentHasImages(response);
}
});
new PollingCheck() {
@Override
protected boolean check() {
return handler.hasCalledHandleMessage();
}
}.run();
assertEquals(1, handler.getMsgArg1());
}
private static void waitForFlingDone(WebViewOnUiThread webview) {
class ScrollDiffPollingCheck extends PollingCheck {
private static final long TIME_SLICE = 50;
WebViewOnUiThread mWebView;
private int mScrollX;
private int mScrollY;
ScrollDiffPollingCheck(WebViewOnUiThread webview) {
mWebView = webview;
mScrollX = mWebView.getScrollX();
mScrollY = mWebView.getScrollY();
}
@Override
protected boolean check() {
try {
Thread.sleep(TIME_SLICE);
} catch (InterruptedException e) {
// Intentionally ignored.
}
int newScrollX = mWebView.getScrollX();
int newScrollY = mWebView.getScrollY();
boolean flingDone = newScrollX == mScrollX && newScrollY == mScrollY;
mScrollX = newScrollX;
mScrollY = newScrollY;
return flingDone;
}
}
new ScrollDiffPollingCheck(webview).run();
}
public void testPageScroll() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
String p = "<p style=\"height:" + dimension + "px;\">" +
"Scroll by half the size of the page.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ p + "</body></html>", "text/html", null);
// Wait for UI thread to settle and receive page dimentions from renderer
// such that we can invoke page down.
new PollingCheck() {
@Override
protected boolean check() {
return mOnUiThread.pageDown(false);
}
}.run();
do {
waitForFlingDone(mOnUiThread);
} while (mOnUiThread.pageDown(false));
waitForFlingDone(mOnUiThread);
int bottomScrollY = mOnUiThread.getScrollY();
assertTrue(mOnUiThread.pageUp(false));
do {
waitForFlingDone(mOnUiThread);
} while (mOnUiThread.pageUp(false));
waitForFlingDone(mOnUiThread);
int topScrollY = mOnUiThread.getScrollY();
// jump to the bottom
assertTrue(mOnUiThread.pageDown(true));
waitForFlingDone(mOnUiThread);
assertEquals(bottomScrollY, mOnUiThread.getScrollY());
// jump to the top
assertTrue(mOnUiThread.pageUp(true));
waitForFlingDone(mOnUiThread);
assertEquals(topScrollY, mOnUiThread.getScrollY());
}
public void testGetContentHeight() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mOnUiThread.loadDataAndWaitForCompletion(
"<html><body></body></html>", "text/html", null);
new PollingCheck() {
@Override
protected boolean check() {
return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0
&& mOnUiThread.getHeight() != 0;
}
}.run();
assertEquals(mOnUiThread.getHeight(),
mOnUiThread.getContentHeight() * mOnUiThread.getScale(), 2f);
final int pageHeight = 600;
// set the margin to 0
final String p = "<p style=\"height:" + pageHeight
+ "px;margin:0px auto;\">Get the height of HTML content.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
new PollingCheck() {
@Override
protected boolean check() {
return mOnUiThread.getContentHeight() > pageHeight;
}
}.run();
final int extraSpace = mOnUiThread.getContentHeight() - pageHeight;
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ p + "</body></html>", "text/html", null);
new PollingCheck() {
@Override
protected boolean check() {
return pageHeight + pageHeight + extraSpace == mOnUiThread.getContentHeight();
}
}.run();
}
@UiThreadTest
public void testPlatformNotifications() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebView.enablePlatformNotifications();
WebView.disablePlatformNotifications();
}
@UiThreadTest
public void testAccessPluginList() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
assertNotNull(WebView.getPluginList());
// can not find a way to install plugins
mWebView.refreshPlugins(false);
}
@UiThreadTest
public void testDestroy() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// Create a new WebView, since we cannot call destroy() on a view in the hierarchy
WebView localWebView = new WebView(getActivity());
localWebView.destroy();
}
public void testFlingScroll() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
final int dimension = 10 * Math.max(metrics.widthPixels, metrics.heightPixels);
String p = "<p style=\"height:" + dimension + "px;" +
"width:" + dimension + "px\">Test fling scroll.</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
new PollingCheck() {
@Override
protected boolean check() {
return mOnUiThread.getContentHeight() >= dimension;
}
}.run();
getInstrumentation().waitForIdleSync();
final int previousScrollX = mOnUiThread.getScrollX();
final int previousScrollY = mOnUiThread.getScrollY();
mOnUiThread.flingScroll(100, 100);
new PollingCheck() {
@Override
protected boolean check() {
return mOnUiThread.getScrollX() > previousScrollX &&
mOnUiThread.getScrollY() > previousScrollY;
}
}.run();
}
public void testRequestFocusNodeHref() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
final String links = "<DL><p><DT><A HREF=\"" + url1
+ "\">HTML_URL1</A><DT><A HREF=\"" + url2
+ "\">HTML_URL2</A></DL><p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + links + "</body></html>", "text/html", null);
getInstrumentation().waitForIdleSync();
final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
final Message hrefMsg = new Message();
hrefMsg.setTarget(handler);
// focus on first link
handler.reset();
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
mOnUiThread.requestFocusNodeHref(hrefMsg);
new PollingCheck() {
@Override
protected boolean check() {
boolean done = false;
if (handler.hasCalledHandleMessage()) {
if (handler.mResultUrl != null) {
done = true;
} else {
handler.reset();
Message newMsg = new Message();
newMsg.setTarget(handler);
mOnUiThread.requestFocusNodeHref(newMsg);
}
}
return done;
}
}.run();
assertEquals(url1, handler.getResultUrl());
// focus on second link
handler.reset();
final Message hrefMsg2 = new Message();
hrefMsg2.setTarget(handler);
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
mOnUiThread.requestFocusNodeHref(hrefMsg2);
new PollingCheck() {
@Override
protected boolean check() {
boolean done = false;
final String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
if (handler.hasCalledHandleMessage()) {
if (handler.mResultUrl != null &&
handler.mResultUrl.equals(url2)) {
done = true;
} else {
handler.reset();
Message newMsg = new Message();
newMsg.setTarget(handler);
mOnUiThread.requestFocusNodeHref(newMsg);
}
}
return done;
}
}.run();
assertEquals(url2, handler.getResultUrl());
mOnUiThread.requestFocusNodeHref(null);
}
public void testRequestImageRef() throws Exception, Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final class ImageLoaded {
public boolean mImageLoaded;
@JavascriptInterface
public void loaded() {
mImageLoaded = true;
}
}
final ImageLoaded imageLoaded = new ImageLoaded();
runTestOnUiThread(new Runnable() {
public void run() {
mOnUiThread.getSettings().setJavaScriptEnabled(true);
}
});
mOnUiThread.addJavascriptInterface(imageLoaded, "imageLoaded");
AssetManager assets = getActivity().getAssets();
Bitmap bitmap = BitmapFactory.decodeStream(assets.open(TestHtmlConstants.LARGE_IMG_URL));
int imgWidth = bitmap.getWidth();
int imgHeight = bitmap.getHeight();
startWebServer(false);
final String imgUrl = mWebServer.getAssetUrl(TestHtmlConstants.LARGE_IMG_URL);
mOnUiThread.loadDataAndWaitForCompletion(
"<html><head><title>Title</title><style type=\"text/css\">"
+ "#imgElement { -webkit-transform: translate3d(0,0,1); }"
+ "#imgElement.finish { -webkit-transform: translate3d(0,0,0);"
+ " -webkit-transition-duration: 1ms; }</style>"
+ "<script type=\"text/javascript\">function imgLoad() {"
+ "imgElement = document.getElementById('imgElement');"
+ "imgElement.addEventListener('webkitTransitionEnd',"
+ "function(e) { imageLoaded.loaded(); });"
+ "imgElement.className = 'finish';}</script>"
+ "</head><body><img id=\"imgElement\" src=\"" + imgUrl
+ "\" width=\"" + imgWidth + "\" height=\"" + imgHeight
+ "\" onLoad=\"imgLoad()\"/></body></html>", "text/html", null);
new PollingCheck() {
@Override
protected boolean check() {
return imageLoaded.mImageLoaded;
}
}.run();
getInstrumentation().waitForIdleSync();
final HrefCheckHandler handler = new HrefCheckHandler(mWebView.getHandler().getLooper());
final Message msg = new Message();
msg.setTarget(handler);
// touch the image
handler.reset();
int[] location = mOnUiThread.getLocationOnScreen();
long time = SystemClock.uptimeMillis();
getInstrumentation().sendPointerSync(
MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN,
location[0] + imgWidth / 2,
location[1] + imgHeight / 2, 0));
getInstrumentation().waitForIdleSync();
mOnUiThread.requestImageRef(msg);
new PollingCheck() {
@Override
protected boolean check() {
boolean done = false;
if (handler.hasCalledHandleMessage()) {
if (handler.mResultUrl != null) {
done = true;
} else {
handler.reset();
Message newMsg = new Message();
newMsg.setTarget(handler);
mOnUiThread.requestImageRef(newMsg);
}
}
return done;
}
}.run();
assertEquals(imgUrl, handler.mResultUrl);
}
@UiThreadTest
public void testDebugDump() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mWebView.debugDump();
}
public void testGetHitTestResult() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String anchor = "<p><a href=\"" + TestHtmlConstants.EXT_WEB_URL1
+ "\">normal anchor</a></p>";
final String blankAnchor = "<p><a href=\"\">blank anchor</a></p>";
final String form = "<p><form><input type=\"text\" name=\"Test\"><br>"
+ "<input type=\"submit\" value=\"Submit\"></form></p>";
String phoneNo = "3106984000";
final String tel = "<p><a href=\"tel:" + phoneNo + "\">Phone</a></p>";
String email = "test@gmail.com";
final String mailto = "<p><a href=\"mailto:" + email + "\">Email</a></p>";
String location = "shanghai";
final String geo = "<p><a href=\"geo:0,0?q=" + location + "\">Location</a></p>";
mOnUiThread.loadDataWithBaseURLAndWaitForCompletion("fake://home",
"<html><body>" + anchor + blankAnchor + form + tel + mailto +
geo + "</body></html>", "text/html", "UTF-8", null);
getInstrumentation().waitForIdleSync();
// anchor
moveFocusDown();
HitTestResult hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType());
assertEquals(TestHtmlConstants.EXT_WEB_URL1, hitTestResult.getExtra());
// blank anchor
moveFocusDown();
hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.SRC_ANCHOR_TYPE, hitTestResult.getType());
assertEquals("fake://home", hitTestResult.getExtra());
// text field
moveFocusDown();
hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.EDIT_TEXT_TYPE, hitTestResult.getType());
assertNull(hitTestResult.getExtra());
// submit button
moveFocusDown();
hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.UNKNOWN_TYPE, hitTestResult.getType());
assertNull(hitTestResult.getExtra());
// phone number
moveFocusDown();
hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.PHONE_TYPE, hitTestResult.getType());
assertEquals(phoneNo, hitTestResult.getExtra());
// email
moveFocusDown();
hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.EMAIL_TYPE, hitTestResult.getType());
assertEquals(email, hitTestResult.getExtra());
// geo address
moveFocusDown();
hitTestResult = mOnUiThread.getHitTestResult();
assertEquals(HitTestResult.GEO_TYPE, hitTestResult.getType());
assertEquals(location, hitTestResult.getExtra());
}
public void testSetInitialScale() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final String p = "<p style=\"height:1000px;width:1000px\">Test setInitialScale.</p>";
final float defaultScale =
getInstrumentation().getTargetContext().getResources().getDisplayMetrics().density;
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
}
}.run();
mOnUiThread.setInitialScale(0);
// modify content to fool WebKit into re-loading
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "2" + "</body></html>", "text/html", null);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
}
}.run();
mOnUiThread.setInitialScale(50);
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "3" + "</body></html>", "text/html", null);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return Math.abs(0.5 - mOnUiThread.getScale()) < .01f;
}
}.run();
mOnUiThread.setInitialScale(0);
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "4" + "</body></html>", "text/html", null);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
}
}.run();
}
@UiThreadTest
public void testClearHistory() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
startWebServer(false);
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
pollingCheckWebBackForwardList(url1, 0, 1);
mOnUiThread.loadUrlAndWaitForCompletion(url2);
pollingCheckWebBackForwardList(url2, 1, 2);
mOnUiThread.loadUrlAndWaitForCompletion(url3);
pollingCheckWebBackForwardList(url3, 2, 3);
mWebView.clearHistory();
// only current URL is left after clearing
pollingCheckWebBackForwardList(url3, 0, 1);
}
@UiThreadTest
public void testSaveAndRestoreState() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
// nothing to save
assertNull(mWebView.saveState(new Bundle()));
startWebServer(false);
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
String url2 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
String url3 = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL3);
// make a history list
mOnUiThread.loadUrlAndWaitForCompletion(url1);
pollingCheckWebBackForwardList(url1, 0, 1);
mOnUiThread.loadUrlAndWaitForCompletion(url2);
pollingCheckWebBackForwardList(url2, 1, 2);
mOnUiThread.loadUrlAndWaitForCompletion(url3);
pollingCheckWebBackForwardList(url3, 2, 3);
// save the list
Bundle bundle = new Bundle();
WebBackForwardList saveList = mWebView.saveState(bundle);
assertNotNull(saveList);
assertEquals(3, saveList.getSize());
assertEquals(2, saveList.getCurrentIndex());
assertEquals(url1, saveList.getItemAtIndex(0).getUrl());
assertEquals(url2, saveList.getItemAtIndex(1).getUrl());
assertEquals(url3, saveList.getItemAtIndex(2).getUrl());
// change the content to a new "blank" web view without history
final WebView newWebView = new WebView(getActivity());
WebBackForwardList copyListBeforeRestore = newWebView.copyBackForwardList();
assertNotNull(copyListBeforeRestore);
assertEquals(0, copyListBeforeRestore.getSize());
// restore the list
final WebBackForwardList restoreList = newWebView.restoreState(bundle);
assertNotNull(restoreList);
assertEquals(3, restoreList.getSize());
assertEquals(2, saveList.getCurrentIndex());
// wait for the list items to get inflated
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return restoreList.getItemAtIndex(0).getUrl() != null &&
restoreList.getItemAtIndex(1).getUrl() != null &&
restoreList.getItemAtIndex(2).getUrl() != null;
}
}.run();
assertEquals(url1, restoreList.getItemAtIndex(0).getUrl());
assertEquals(url2, restoreList.getItemAtIndex(1).getUrl());
assertEquals(url3, restoreList.getItemAtIndex(2).getUrl());
WebBackForwardList copyListAfterRestore = newWebView.copyBackForwardList();
assertNotNull(copyListAfterRestore);
assertEquals(3, copyListAfterRestore.getSize());
assertEquals(2, copyListAfterRestore.getCurrentIndex());
assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl());
assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl());
assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl());
}
public void testSetWebViewClient() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final ScaleChangedWebViewClient webViewClient = new ScaleChangedWebViewClient();
mOnUiThread.setWebViewClient(webViewClient);
startWebServer(false);
assertFalse(webViewClient.onScaleChangedCalled());
String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url1);
pollingCheckForCanZoomIn();
assertTrue(mOnUiThread.zoomIn());
webViewClient.waitForScaleChanged();
}
public void testRequestChildRectangleOnScreen() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
DisplayMetrics metrics = mOnUiThread.getDisplayMetrics();
final int dimension = 2 * Math.max(metrics.widthPixels, metrics.heightPixels);
String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\">&nbsp;</p>";
mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
+ "</body></html>", "text/html", null);
new PollingCheck() {
@Override
protected boolean check() {
return mOnUiThread.getContentHeight() >= dimension;
}
}.run();
int origX = mOnUiThread.getScrollX();
int origY = mOnUiThread.getScrollY();
int half = dimension / 2;
Rect rect = new Rect(half, half, half + 1, half + 1);
assertTrue(mOnUiThread.requestChildRectangleOnScreen(mWebView, rect, true));
assertTrue(mOnUiThread.getScrollX() > origX);
assertTrue(mOnUiThread.getScrollY() > origY);
}
public void testSetDownloadListener() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final CountDownLatch resultLatch = new CountDownLatch(1);
final class MyDownloadListener implements DownloadListener {
public String url;
public String mimeType;
public long contentLength;
public String contentDisposition;
@Override
public void onDownloadStart(String url, String userAgent, String contentDisposition,
String mimetype, long contentLength) {
this.url = url;
this.mimeType = mimetype;
this.contentLength = contentLength;
this.contentDisposition = contentDisposition;
resultLatch.countDown();
}
}
final String mimeType = "application/octet-stream";
final int length = 100;
final MyDownloadListener listener = new MyDownloadListener();
startWebServer(false);
final String url = mWebServer.getBinaryUrl(mimeType, length);
// By default, WebView sends an intent to ask the system to
// handle loading a new URL. We set WebViewClient as
// WebViewClient.shouldOverrideUrlLoading() returns false, so
// the WebView will load the new URL.
mOnUiThread.setDownloadListener(listener);
mOnUiThread.getSettings().setJavaScriptEnabled(true);
mOnUiThread.loadDataAndWaitForCompletion(
"<html><body onload=\"window.location = \'" + url + "\'\"></body></html>",
"text/html", null);
// Wait for layout to complete before setting focus.
getInstrumentation().waitForIdleSync();
assertTrue(resultLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
assertEquals(url, listener.url);
assertTrue(listener.contentDisposition.contains("test.bin"));
assertEquals(length, listener.contentLength);
assertEquals(mimeType, listener.mimeType);
}
@UiThreadTest
public void testSetLayoutParams() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(600, 800);
mWebView.setLayoutParams(params);
assertSame(params, mWebView.getLayoutParams());
}
@UiThreadTest
public void testSetMapTrackballToArrowKeys() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mWebView.setMapTrackballToArrowKeys(true);
}
public void testSetNetworkAvailable() throws Exception {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
startWebServer(false);
String url = mWebServer.getAssetUrl(TestHtmlConstants.NETWORK_STATE_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
assertEquals("ONLINE", mOnUiThread.getTitle());
mOnUiThread.setNetworkAvailable(false);
// Wait for the DOM to receive notification of the network state change.
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals("OFFLINE");
}
}.run();
mOnUiThread.setNetworkAvailable(true);
// Wait for the DOM to receive notification of the network state change.
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals("ONLINE");
}
}.run();
}
public void testSetWebChromeClient() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
final class MockWebChromeClient extends WaitForProgressClient {
private boolean mOnProgressChanged = false;
public MockWebChromeClient() {
super(mOnUiThread);
}
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
mOnProgressChanged = true;
}
public boolean onProgressChangedCalled() {
return mOnProgressChanged;
}
}
final MockWebChromeClient webChromeClient = new MockWebChromeClient();
mOnUiThread.setWebChromeClient(webChromeClient);
getInstrumentation().waitForIdleSync();
assertFalse(webChromeClient.onProgressChangedCalled());
startWebServer(false);
final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
mOnUiThread.loadUrlAndWaitForCompletion(url);
getInstrumentation().waitForIdleSync();
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return webChromeClient.onProgressChangedCalled();
}
}.run();
}
public void testPauseResumeTimers() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
class Monitor {
private boolean mIsUpdated;
@JavascriptInterface
public synchronized void update() {
mIsUpdated = true;
notify();
}
public synchronized boolean waitForUpdate() {
while (!mIsUpdated) {
try {
// This is slightly flaky, as we can't guarantee that
// this is a sufficient time limit, but there's no way
// around this.
wait(1000);
if (!mIsUpdated) {
return false;
}
} catch (InterruptedException e) {
}
}
mIsUpdated = false;
return true;
}
};
final Monitor monitor = new Monitor();
final String updateMonitorHtml = "<html>" +
"<body onload=\"monitor.update();\"></body></html>";
// Test that JavaScript is executed even with timers paused.
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(monitor, "monitor");
mWebView.pauseTimers();
mOnUiThread.loadDataAndWaitForCompletion(updateMonitorHtml,
"text/html", null);
}
});
assertTrue(monitor.waitForUpdate());
// Start a timer and test that it does not fire.
mOnUiThread.loadDataAndWaitForCompletion(
"<html><body onload='setTimeout(function(){monitor.update();},100)'>" +
"</body></html>", "text/html", null);
assertFalse(monitor.waitForUpdate());
// Resume timers and test that the timer fires.
mOnUiThread.resumeTimers();
assertTrue(monitor.waitForUpdate());
}
// verify query parameters can be passed correctly to android asset files
public void testAndroidAssetQueryParam() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
// test passing a parameter
String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.PARAM_ASSET_URL+"?val=SUCCESS");
mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
assertEquals("SUCCESS", mOnUiThread.getTitle());
}
// verify anchors work correctly for android asset files
public void testAndroidAssetAnchor() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
WebSettings settings = mOnUiThread.getSettings();
settings.setJavaScriptEnabled(true);
// test using an anchor
String fileUrl = TestHtmlConstants.getFileUrl(TestHtmlConstants.ANCHOR_ASSET_URL+"#anchor");
mOnUiThread.loadUrlAndWaitForCompletion(fileUrl);
assertEquals("anchor", mOnUiThread.getTitle());
}
public void testEvaluateJavascript() {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mOnUiThread.getSettings().setJavaScriptEnabled(true);
mOnUiThread.loadUrlAndWaitForCompletion("about:blank");
EvaluateJsResultPollingCheck jsResult = new EvaluateJsResultPollingCheck("2");
mOnUiThread.evaluateJavascript("1+1", jsResult);
jsResult.run();
jsResult = new EvaluateJsResultPollingCheck("9");
mOnUiThread.evaluateJavascript("1+1; 4+5", jsResult);
jsResult.run();
final String EXPECTED_TITLE = "test";
mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null);
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return mOnUiThread.getTitle().equals(EXPECTED_TITLE);
}
}.run();
}
// Verify Print feature can create a PDF file with a correct preamble.
public void testPrinting() throws Throwable {
if (!NullWebViewUtils.isWebViewAvailable()) {
return;
}
mOnUiThread.loadDataAndWaitForCompletion("<html><head></head>" +
"<body>foo</body></html>",
"text/html", null);
final PrintDocumentAdapter adapter = mOnUiThread.createPrintDocumentAdapter();
printDocumentStart(adapter);
PrintAttributes attributes = new PrintAttributes.Builder()
.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
.setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300))
.setMinMargins(PrintAttributes.Margins.NO_MARGINS)
.build();
final WebViewCtsActivity activity = getActivity();
final File file = activity.getFileStreamPath(PRINTER_TEST_FILE);
final ParcelFileDescriptor descriptor = ParcelFileDescriptor.open(file,
ParcelFileDescriptor.parseMode("w"));
final FutureTask<Boolean> result =
new FutureTask<Boolean>(new Callable<Boolean>() {
public Boolean call() {
return true;
}
});
printDocumentLayout(adapter, null, attributes,
new LayoutResultCallback() {
// Called on UI thread
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
savePrintedPage(adapter, descriptor, result);
}
});
try {
result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
assertTrue(file.length() > 0);
FileInputStream in = new FileInputStream(file);
byte[] b = new byte[PDF_PREAMBLE.length()];
in.read(b);
String preamble = new String(b);
assertEquals(PDF_PREAMBLE, preamble);
} finally {
// close the descriptor, if not closed already.
descriptor.close();
file.delete();
}
}
private void savePrintedPage(final PrintDocumentAdapter adapter,
final ParcelFileDescriptor descriptor, final FutureTask<Boolean> result) {
adapter.onWrite(new PageRange[] {PageRange.ALL_PAGES}, descriptor,
new CancellationSignal(),
new WriteResultCallback() {
@Override
public void onWriteFinished(PageRange[] pages) {
try {
descriptor.close();
result.run();
} catch (IOException ex) {
fail("Failed file operation: " + ex.toString());
}
}
});
}
private void printDocumentStart(final PrintDocumentAdapter adapter) {
mOnUiThread.runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.onStart();
}
});
}
private void printDocumentLayout(final PrintDocumentAdapter adapter,
final PrintAttributes oldAttributes, final PrintAttributes newAttributes,
final LayoutResultCallback layoutResultCallback) {
mOnUiThread.runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.onLayout(oldAttributes, newAttributes, new CancellationSignal(),
layoutResultCallback, null);
}
});
}
private static class HrefCheckHandler extends Handler {
private boolean mHadRecieved;
private String mResultUrl;
public HrefCheckHandler(Looper looper) {
super(looper);
}
public boolean hasCalledHandleMessage() {
return mHadRecieved;
}
public String getResultUrl() {
return mResultUrl;
}
public void reset(){
mResultUrl = null;
mHadRecieved = false;
}
@Override
public void handleMessage(Message msg) {
mResultUrl = msg.getData().getString("url");
mHadRecieved = true;
}
}
private void moveFocusDown() throws Throwable {
// send down key and wait for idle
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
// waiting for idle isn't always sufficient for the key to be fully processed
Thread.sleep(500);
}
private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex,
final int size) {
new PollingCheck() {
@Override
protected boolean check() {
WebBackForwardList list = mWebView.copyBackForwardList();
return checkWebBackForwardList(list, currUrl, currIndex, size);
}
}.run();
}
private boolean checkWebBackForwardList(WebBackForwardList list, String currUrl,
int currIndex, int size) {
return (list != null)
&& (list.getSize() == size)
&& (list.getCurrentIndex() == currIndex)
&& list.getItemAtIndex(currIndex).getUrl().equals(currUrl);
}
private void assertGoBackOrForwardBySteps(boolean expected, int steps) {
// skip if steps equals to 0
if (steps == 0)
return;
int start = steps > 0 ? 1 : steps;
int end = steps > 0 ? steps : -1;
// check all the steps in the history
for (int i = start; i <= end; i++) {
assertEquals(expected, mWebView.canGoBackOrForward(i));
// shortcut methods for one step
if (i == 1) {
assertEquals(expected, mWebView.canGoForward());
} else if (i == -1) {
assertEquals(expected, mWebView.canGoBack());
}
}
}
private boolean isPictureFilledWithColor(Picture picture, int color) {
if (picture.getWidth() == 0 || picture.getHeight() == 0)
return false;
Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(),
Config.ARGB_8888);
picture.draw(new Canvas(bitmap));
for (int i = 0; i < bitmap.getWidth(); i ++) {
for (int j = 0; j < bitmap.getHeight(); j ++) {
if (color != bitmap.getPixel(i, j)) {
return false;
}
}
}
return true;
}
/**
* Waits at least MIN_SCROLL_WAIT_MS for scrolling to start. Once started,
* scrolling is checked every SCROLL_WAIT_INTERVAL_MS for changes. Once
* changes have stopped, the function exits. If no scrolling has happened
* then the function exits after MIN_SCROLL_WAIT milliseconds.
* @param previousScrollY The Y scroll position prior to waiting.
*/
private void waitForScrollingComplete(int previousScrollY)
throws InterruptedException {
int scrollY = previousScrollY;
// wait at least MIN_SCROLL_WAIT for something to happen.
long noChangeMinWait = SystemClock.uptimeMillis() + MIN_SCROLL_WAIT_MS;
boolean scrollChanging = false;
boolean scrollChanged = false;
boolean minWaitExpired = false;
while (scrollChanging || (!scrollChanged && !minWaitExpired)) {
Thread.sleep(SCROLL_WAIT_INTERVAL_MS);
int oldScrollY = scrollY;
scrollY = mOnUiThread.getScrollY();
scrollChanging = (scrollY != oldScrollY);
scrollChanged = (scrollY != previousScrollY);
minWaitExpired = (SystemClock.uptimeMillis() > noChangeMinWait);
}
}
private void pollingCheckForCanZoomIn() {
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return mOnUiThread.canZoomIn();
}
}.run();
}
final class ScaleChangedWebViewClient extends WaitForLoadedClient {
private boolean mOnScaleChangedCalled = false;
public ScaleChangedWebViewClient() {
super(mOnUiThread);
}
@Override
public void onScaleChanged(WebView view, float oldScale, float newScale) {
super.onScaleChanged(view, oldScale, newScale);
synchronized (this) {
mOnScaleChangedCalled = true;
}
}
public void waitForScaleChanged() {
new PollingCheck(TEST_TIMEOUT) {
@Override
protected boolean check() {
return onScaleChangedCalled();
}
}.run();
synchronized (this) {
mOnScaleChangedCalled = false;
}
}
public synchronized boolean onScaleChangedCalled() {
return mOnScaleChangedCalled;
}
}
}